mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
* General: Connect to AYON server (base) (#3924) * implemented 'get_workfile_info' in entities * removed 'prepare_asset_update_data' which is not used * disable settings and project manager if in v4 mode * prepared conversion helper functions for v4 entities * prepared conversion functions for hero versions * fix hero versions * implemented get_archived_representations * fix get latest versions * return prepared changes * handle archived representation * raise exception on failed json conversion * map archived to active properly * make sure default fields are added * fix conversion of hero version entity * fix conversion of archived representations * fix some conversions of representations and versions * changed active behavior in queries * fixed hero versions * implemented basic thumbnail caching * added raw variants of crud methods * implemented methods to get and create thumbnail * fix from flat dict * implemented some basic folder conversion for updates * fix thumbnail updates for version * implemented v4 thumbnail integrator * simplified data mapping * 'get_thumbnail' function also expect entity type and entity id for which is the thumbnail received * implemented 'get_thumbnail' for server * fix how thumbnail id is received from entity * removed unnecessary method 'get_thumbnail_id_from_source' * implemented thumbnail resolver for v4 * removed unnecessary print * move create and delete project directly to server api * disable local settings action too on v4 * OP-3521 - added method to check and download updated addons from v4 server * OP-3521 - added more descriptive error message for missing source * OP-3521 - added default implementation of addon downloader to import * OP-3521 - added check for dependency package zips WIP - server doesn't contain required endpoint. Testing only with mockup data for now. * OP-3521 - fixed parsing of DependencyItem Added Server Url type and ServerAddonDownloader - v4 server doesn't know its own DNS for static files so it is sending unique name and url must be created during runtime. * OP-3521 - fixed creation of targed directories * change nev keys to look for and don't set them automatically * fix task type conversion * implemented base of loading v4 addons in v3 * Refactored argument name in Downloaders * Updated parsing to DependencyItem according to current schema * Implemented downloading of package from server * Updated resolving of failures Uses Enum items. * Introduced passing of authorization token Better to inject it than to have it from env var. * Remove weird parsing of server_url Not necessary, endpoints have same prefix. * Fix doubling asset version name in addons folder Zip file should already contain `addonName_addonVersion` as first subfolder * Fix doubling asset version name in addons folder Zip file should already contain `addonName_addonVersion` as first subfolder * Made server_endpoint optional Argument should be better for testing, but for calling from separate methods it would be better to encapsulate it. Removed unwanted temporary productionPackage value * Use existing method to pull addon info from Server to load v4 version of addon * Raise exception when server doesn't have any production dependency package * added ability to specify v3 alias of addon name * expect v3_alias as uppered constant * Re-implemented method to get addon info Previous implementation wouldn't work in Python2 hosts. Will be refactored in the future. * fix '__getattr__' * added ayon api to pyproject.toml and lock file * use ayon api in common connection * added mapping for label * use ayon_api in client codebase * separated clearing cache of url and username * bump ayon api version * rename env 'OP4_TEST' to 'USE_AYON_SERVER' * Move and renamend get_addons_info to get_addons_info_as_dict in addon_distribution Should be moved to ayon_api later * Replaced requests calls with ayon_api * Replaced OP4_TEST_ENABLED with AYON_SERVER_ENABLED fixed endpoints * Hound * Hound * OP-3521 - fix wrong key in get_representation_parents parents overloads parents * OP-3521 - changes for v4 of SiteSync addon * OP-3521 - fix names * OP-3521 - remove storing project_name It should be safer to go thorug self.dbcon apparently * OP-3521 - remove unwanted "context["folder"]" can be only in dummy test data * OP-3521 - move site sync loaders to addon * Use only project instead of self.project * OP-3521 - added missed get_progress_for_repre * base of settings conversion script * simplified ayon functions in start.py * added loading of settings from ayon server * added a note about colors * fix global and local settings functions * AvalonMongoDB is not using mongo connection on ayon server enabled * 'get_dynamic_modules_dirs' is not checking system settings for paths in setting * log viewer is disabled when ayon server is enabled * basic logic of enabling/disabled addons * don't use mongo logging if ayon server is enabled * update ayon api * bump ayon api again * use ayon_api to get addons info in modules/base * update ayon api * moved helper functions to get addons and dependencies dir to common functions * Initialization of AddonInfo is not crashing on unkonwn sources * renamed 'DependencyDownloader' to 'AyonServerDownloader' * renamed function 'default_addon_downloader' to 'get_default_addon_downloader' * Added ability to convert 'WebAddonSource' to 'ServerResourceSorce' * missing dependency package on server won't cause crash * data sent to downloaders don't contain ayon specific headers * modified addon distribution to not duplicate 'ayon_api' functionality * fix doubled function defintioin * unzip client file to addon destination * formatting - unify quotes * disable usage of mongo connection if in ayon mode * renamed window.py to login_window.py * added webpublisher settings conversion * added maya conversion function * reuse variable * reuse variable (similar to previous commit) * fix ayon addons loading * fix typo 'AyonSettingsCahe' -> 'AyonSettingsCache' * fix enabled state changes * fix rr_path in royal render conversion * avoid mongo calls in AYON state * implemented custom AYON start script * fix formatting (after black) * ayon_start cleanup * 'get_addons_dir' and 'get_dependencies_dir' store value to environment variable * add docstrings to local dir functions * addon info has full name * fix modules enabled states * removed unused 'run_disk_mapping_commands' * removed ayon logic from 'start.py' * fix warning message * renamed 'openpype_common' to 'ayon_common' * removed unused import * don't import igniter * removed startup validations of third parties * change what's shown in version info * fix which keys are applied from ayon values * fix method name * get applications from attribs * Implemented UI basics to be able change user or logout * merged server.py and credentials.py * add more metadata to urls * implemented change token * implemented change user ui functionality * implemented change user ui * modify window to handle username and token value * pass username to add server * fix show UI cases * added loggin action to tray * update ayon api * added missing dependency * convert applications to config in a right way * initial implementation of 'nuke' settings conversion * removed few nuke comments * implemented hiero conversion * added imageio conversion * added run ayon tray script * fix few settings conversions * Renamed class of source classes as they are not just for addons * implemented objec to track source transfer progress * Implemented distribution item with multiple sources * Implemented ayon distribution wrapper to care about multiple things during distribution * added 'cleanup' method for downlaoders * download gets tranfer progress object * Change UploadState enum * added missing imports * use AyonDistribution in ayon_start.py * removed unused functions * removed implemented TODOs * fix import * fix key used for Web source * removed temp development fix * formatting fix * keep information if source require distribution * handle 'require_distribution' attribute in distribution process * added path attribute to server source * added option to pass addons infor to ayon distribution * fix tests * fix formatting * Fix typo * Fix typo * remove '_try_convert_to_server_source' * renamed attributes and methods to match their content * it is possible to pass dependency package info to AyonDistribution * fix called methods in tests * added public properties for error message and error detail * Added filename to WebSourceInfo Useful for GDrive sharable links where target file name is unknown/unparsable, it should be provided explicitly. * unify source conversion by adding 'convert_source' function * Fix error message Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * added docstring for 'transfer_progress' * don't create metadata file on read * added few docstrings * add default folder fields to folder/task queries * fix generators * add dependencies when runnign from code * add sys paths from distribution to pythonpath env * fix missing applications * added missing conversions for maya renderers * fix formatting * update ayon api * fix hashes in lock file * Use better exception Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> * Use Python 3 syntax Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> * apply some of sugested changes in ayon_start * added some docstrings and suggested modifications * copy create env from develop * fix rendersettings conversion * change code by suggestions * added missing args to docstring * added missing docstrings * separated downloader and download factory * fix ayon settings * added some basic file docstring to ayon_settings * join else conditions * fix project settings conversion * fix created at conversion * fix workfile info query * fix publisher UI * added utils function 'get_ayon_appdirs' * fix 'get_all_current_info' * fix server url assignment when url is set * updated ayon api * added utils functions to create local site id for ayon * added helper functions to create global connection * create global connection in ayon start to start use site id * use ayon site id in ayon mode * formatting cleanup * added header docstring * fixes after ayon_api update * load addons from ynput appdirs * fix function call * added docstring * update ayon pyton api * fix settings access * use ayon_api to get root overrides in Anatomy * bumbayon version to 0.1.13 * nuke: fixing settings keys from settings * fix burnins definitions * change v4 to AYON in thumbnail integrate * fix one more v4 information * Fixes after rebase * fix extract burnin conversion * additional fix of extract burnin * SiteSync:added missed loaders or v3 compatibility (#4587) * Added site sync loaders for v3 compatibility * Fix get_progress_for_repre * use 'files.name' instead of 'files.baseName' * update ayon api to 0.1.14 * add common to include files * change arguments for hero version creation * skip shotgrid settings conversion if different ayon addon is used * added ayon icons * fix labels of application variants * added option to show login window always on top * login window on invalid credentials is always on top * update ayon api * update ayon api * add entityType to project and folders * AYON: Editorial hierarchy creation (#4699) * disable extract hierarchy avalon when ayon mode is enabled * implemented extract hierarchy to AYON --------- Co-authored-by: Petr Kalis <petr.kalis@gmail.com> Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> Co-authored-by: Jakub Jezek <jakubjezek001@gmail.com> * replace 'legacy_io' with context functions in load plugins * added 'get_global_context' to pipeline init * use context getters instead of legacy_io in publish plugins * use data on context instead of 'legacy_io' in submit publish job * skip query of asset docs in collect nuke reads * use context functions on other places * 'list_looks' expects project name * remove 'get_context_title' * don't pass AvalonMongoDB to prelaunch hooks * change how context is calculated in hiero * implemented function 'get_fps_for_current_context' for maya * initialize '_image_dir' and '_image_prefixes' in init * legacy creator is using 'get_current_project_name' * fill docstrings * use context functions in workfile builders * hound fixes * 'create_workspace_mel' can expect project settings * swapped order of arguments * use information from instance/context data * Use self.project_name in workfiles tool Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Remove outdated todo Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * don't query project document in nuke lib * Fix access to context data * Use right function to get project name Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * fix submit max deadline and swap order of arguments * added 'get_context_label' to nuke * fix import * fix typo 'curent_context' -> 'current_context' * fix project_setting variable * fix submit publish job environments * use task from context * Removed unused import --------- Co-authored-by: Petr Kalis <petr.kalis@gmail.com> Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> Co-authored-by: Jakub Jezek <jakubjezek001@gmail.com>
774 lines
23 KiB
Python
774 lines
23 KiB
Python
import os
|
|
import copy
|
|
import collections
|
|
|
|
from abc import (
|
|
ABCMeta,
|
|
abstractmethod,
|
|
abstractproperty
|
|
)
|
|
|
|
import six
|
|
|
|
from openpype.settings import get_system_settings, get_project_settings
|
|
from openpype.lib import Logger
|
|
from openpype.pipeline.plugin_discover import (
|
|
discover,
|
|
register_plugin,
|
|
register_plugin_path,
|
|
deregister_plugin,
|
|
deregister_plugin_path
|
|
)
|
|
|
|
from .subset_name import get_subset_name
|
|
from .utils import get_next_versions_for_instances
|
|
from .legacy_create import LegacyCreator
|
|
|
|
|
|
class CreatorError(Exception):
|
|
"""Should be raised when creator failed because of known issue.
|
|
|
|
Message of error should be user readable.
|
|
"""
|
|
|
|
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
|
|
"""
|
|
|
|
_log = None
|
|
|
|
def __init__(self, create_context):
|
|
self._create_context = create_context
|
|
|
|
@property
|
|
def log(self):
|
|
"""Logger of the plugin.
|
|
|
|
Returns:
|
|
logging.Logger: Logger with name of the plugin.
|
|
"""
|
|
|
|
if self._log is None:
|
|
self._log = Logger.get_logger(self.__class__.__name__)
|
|
return self._log
|
|
|
|
@property
|
|
def host(self):
|
|
return self._create_context.host
|
|
|
|
@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.
|
|
|
|
Returns:
|
|
CreateContext: Context which initialized the plugin.
|
|
"""
|
|
|
|
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.
|
|
|
|
We should maybe find better name as creation is only one part of it's logic
|
|
and to avoid expectations that it is the same as `avalon.api.Creator`.
|
|
|
|
Single object should be used for multiple instances instead of single
|
|
instance per one creator object. Do not store temp data or mid-process data
|
|
to `self` if it's not Plugin specific.
|
|
|
|
Args:
|
|
project_settings (Dict[str, Any]): Project settings.
|
|
system_settings (Dict[str, Any]): System settings.
|
|
create_context (CreateContext): Context which initialized creator.
|
|
headless (bool): Running in headless mode.
|
|
"""
|
|
|
|
# Label shown in UI
|
|
label = None
|
|
group_label = None
|
|
# Cached group label after first call 'get_group_label'
|
|
_cached_group_label = None
|
|
|
|
# Order in which will be plugin executed (collect & update instances)
|
|
# less == earlier -> Order '90' will be processed before '100'
|
|
order = 100
|
|
|
|
# Variable to store logger
|
|
_log = None
|
|
|
|
# Creator is enabled (Probably does not have reason of existence?)
|
|
enabled = True
|
|
|
|
# Creator (and family) icon
|
|
# - may not be used if `get_icon` is reimplemented
|
|
icon = None
|
|
|
|
# Instance attribute definitions that can be changed per instance
|
|
# - returns list of attribute definitions from
|
|
# `openpype.pipeline.attribute_definitions`
|
|
instance_attr_defs = []
|
|
|
|
# Filtering by host name - can be used to be filtered by host name
|
|
# - used on all hosts when set to 'None' for Backwards compatibility
|
|
# - was added afterwards
|
|
# QUESTION make this required?
|
|
host_name = None
|
|
|
|
def __init__(
|
|
self, project_settings, system_settings, create_context, headless=False
|
|
):
|
|
# Reference to CreateContext
|
|
self.create_context = create_context
|
|
self.project_settings = project_settings
|
|
|
|
# Creator is running in headless mode (without UI elemets)
|
|
# - we may use UI inside processing this attribute should be checked
|
|
self.headless = headless
|
|
|
|
self.apply_settings(project_settings, system_settings)
|
|
|
|
def apply_settings(self, project_settings, system_settings):
|
|
"""Method called on initialization of plugin to apply settings."""
|
|
|
|
pass
|
|
|
|
@property
|
|
def identifier(self):
|
|
"""Identifier of creator (must be unique).
|
|
|
|
Default implementation returns plugin's family.
|
|
"""
|
|
|
|
return self.family
|
|
|
|
@abstractproperty
|
|
def family(self):
|
|
"""Family that plugin represents."""
|
|
|
|
pass
|
|
|
|
@property
|
|
def project_name(self):
|
|
"""Current project name.
|
|
|
|
Returns:
|
|
str: Name of a project.
|
|
"""
|
|
|
|
return self.create_context.project_name
|
|
|
|
@property
|
|
def project_anatomy(self):
|
|
"""Current project anatomy.
|
|
|
|
Returns:
|
|
Anatomy: Project anatomy object.
|
|
"""
|
|
|
|
return self.create_context.project_anatomy
|
|
|
|
@property
|
|
def host(self):
|
|
return self.create_context.host
|
|
|
|
def get_group_label(self):
|
|
"""Group label under which are instances grouped in UI.
|
|
|
|
Default implementation use attributes in this order:
|
|
- 'group_label' -> 'label' -> 'identifier'
|
|
Keep in mind that 'identifier' use 'family' by default.
|
|
|
|
Returns:
|
|
str: Group label that can be used for grouping of instances in UI.
|
|
Group label can be overriden by instance itself.
|
|
"""
|
|
|
|
if self._cached_group_label is None:
|
|
label = self.identifier
|
|
if self.group_label:
|
|
label = self.group_label
|
|
elif self.label:
|
|
label = self.label
|
|
self._cached_group_label = label
|
|
return self._cached_group_label
|
|
|
|
@property
|
|
def log(self):
|
|
"""Logger of the plugin.
|
|
|
|
Returns:
|
|
logging.Logger: Logger with name of the plugin.
|
|
"""
|
|
|
|
if self._log is None:
|
|
self._log = Logger.get_logger(self.__class__.__name__)
|
|
return self._log
|
|
|
|
def _add_instance_to_context(self, instance):
|
|
"""Helper method to add instance to create context.
|
|
|
|
Instances should be stored to DCC workfile metadata to be able reload
|
|
them and also stored to CreateContext in which is creator plugin
|
|
existing at the moment to be able use it without refresh of
|
|
CreateContext.
|
|
|
|
Args:
|
|
instance (CreatedInstance): New created instance.
|
|
"""
|
|
|
|
self.create_context.creator_adds_instance(instance)
|
|
|
|
def _remove_instance_from_context(self, instance):
|
|
"""Helper method to remove instance from create context.
|
|
|
|
Instances must be removed from DCC workfile metadat aand from create
|
|
context in which plugin is existing at the moment of removement to
|
|
propagate the change without restarting create context.
|
|
|
|
Args:
|
|
instance (CreatedInstance): Instance which should be removed.
|
|
"""
|
|
|
|
self.create_context.creator_removed_instance(instance)
|
|
|
|
@abstractmethod
|
|
def create(self):
|
|
"""Create new instance.
|
|
|
|
Replacement of `process` method from avalon implementation.
|
|
- must expect all data that were passed to init in previous
|
|
implementation
|
|
"""
|
|
|
|
pass
|
|
|
|
@abstractmethod
|
|
def collect_instances(self):
|
|
"""Collect existing instances related to this creator plugin.
|
|
|
|
The implementation differs on host abilities. The creator has to
|
|
collect metadata about instance and create 'CreatedInstance' object
|
|
which should be added to 'CreateContext'.
|
|
|
|
Example:
|
|
```python
|
|
def collect_instances(self):
|
|
# Getting existing instances is different per host implementation
|
|
for instance_data in pipeline.list_instances():
|
|
# Process only instances that were created by this creator
|
|
creator_id = instance_data.get("creator_identifier")
|
|
if creator_id == self.identifier:
|
|
# Create instance object from existing data
|
|
instance = CreatedInstance.from_existing(
|
|
instance_data, self
|
|
)
|
|
# Add instance to create context
|
|
self._add_instance_to_context(instance)
|
|
```
|
|
"""
|
|
|
|
pass
|
|
|
|
@abstractmethod
|
|
def update_instances(self, update_list):
|
|
"""Store changes of existing instances so they can be recollected.
|
|
|
|
Args:
|
|
update_list(List[UpdateData]): Gets list of tuples. Each item
|
|
contain changed instance and it's changes.
|
|
"""
|
|
|
|
pass
|
|
|
|
@abstractmethod
|
|
def remove_instances(self, instances):
|
|
"""Method called on instance removement.
|
|
|
|
Can also remove instance metadata from context but should return
|
|
'True' if did so.
|
|
|
|
Args:
|
|
instance(List[CreatedInstance]): Instance objects which should be
|
|
removed.
|
|
"""
|
|
|
|
pass
|
|
|
|
def get_icon(self):
|
|
"""Icon of creator (family).
|
|
|
|
Can return path to image file or awesome icon name.
|
|
"""
|
|
|
|
return self.icon
|
|
|
|
def get_dynamic_data(
|
|
self, variant, task_name, asset_doc, project_name, host_name, instance
|
|
):
|
|
"""Dynamic data for subset name filling.
|
|
|
|
These may be get dynamically created based on current context of
|
|
workfile.
|
|
"""
|
|
|
|
return {}
|
|
|
|
def get_subset_name(
|
|
self,
|
|
variant,
|
|
task_name,
|
|
asset_doc,
|
|
project_name,
|
|
host_name=None,
|
|
instance=None
|
|
):
|
|
"""Return subset name for passed context.
|
|
|
|
CHANGES:
|
|
Argument `asset_id` was replaced with `asset_doc`. It is easier to
|
|
query asset before. In some cases would this method be called multiple
|
|
times and it would be too slow to query asset document on each
|
|
callback.
|
|
|
|
NOTE:
|
|
Asset document is not used yet but is required if would like to use
|
|
task type in subset templates.
|
|
|
|
Method is also called on subset name update. In that case origin
|
|
instance is passed in.
|
|
|
|
Args:
|
|
variant(str): Subset name variant. In most of cases user input.
|
|
task_name(str): For which task subset is created.
|
|
asset_doc(dict): Asset document for which subset is created.
|
|
project_name(str): Project name.
|
|
host_name(str): Which host creates subset.
|
|
instance(CreatedInstance|None): Object of 'CreatedInstance' for
|
|
which is subset name updated. Passed only on subset name
|
|
update.
|
|
"""
|
|
|
|
dynamic_data = self.get_dynamic_data(
|
|
variant, task_name, asset_doc, project_name, host_name, instance
|
|
)
|
|
|
|
return get_subset_name(
|
|
self.family,
|
|
variant,
|
|
task_name,
|
|
asset_doc,
|
|
project_name,
|
|
host_name,
|
|
dynamic_data=dynamic_data,
|
|
project_settings=self.project_settings
|
|
)
|
|
|
|
def get_instance_attr_defs(self):
|
|
"""Plugin attribute definitions.
|
|
|
|
Attribute definitions of plugin that hold data about created instance
|
|
and values are stored to metadata for future usage and for publishing
|
|
purposes.
|
|
|
|
NOTE:
|
|
Convert method should be implemented which should care about updating
|
|
keys/values when plugin attributes change.
|
|
|
|
Returns:
|
|
List[AbstractAttrDef]: Attribute definitions that can be tweaked
|
|
for created instance.
|
|
"""
|
|
|
|
return self.instance_attr_defs
|
|
|
|
@property
|
|
def collection_shared_data(self):
|
|
"""Access to shared data that can be used during creator's collection.
|
|
|
|
Retruns:
|
|
Dict[str, Any]: Shared data.
|
|
|
|
Raises:
|
|
UnavailableSharedData: When called out of collection phase.
|
|
"""
|
|
|
|
return self.create_context.collection_shared_data
|
|
|
|
def set_instance_thumbnail_path(self, instance_id, thumbnail_path=None):
|
|
"""Set path to thumbnail for instance."""
|
|
|
|
self.create_context.thumbnail_paths_by_instance_id[instance_id] = (
|
|
thumbnail_path
|
|
)
|
|
|
|
def get_next_versions_for_instances(self, instances):
|
|
"""Prepare next versions for instances.
|
|
|
|
This is helper method to receive next possible versions for instances.
|
|
It is using context information on instance to receive them, 'asset'
|
|
and 'subset'.
|
|
|
|
Output will contain version by each instance id.
|
|
|
|
Args:
|
|
instances (list[CreatedInstance]): Instances for which to get next
|
|
versions.
|
|
|
|
Returns:
|
|
Dict[str, int]: Next versions by instance id.
|
|
"""
|
|
|
|
return get_next_versions_for_instances(
|
|
self.create_context.project_name, instances
|
|
)
|
|
|
|
|
|
class Creator(BaseCreator):
|
|
"""Creator that has more information for artist to show in UI.
|
|
|
|
Creation requires prepared subset name and instance data.
|
|
"""
|
|
|
|
# GUI Purposes
|
|
# - default_variants may not be used if `get_default_variants` is overriden
|
|
default_variants = []
|
|
|
|
# Default variant used in 'get_default_variant'
|
|
default_variant = None
|
|
|
|
# Short description of family
|
|
# - may not be used if `get_description` is overriden
|
|
description = None
|
|
|
|
# Detailed description of family for artists
|
|
# - may not be used if `get_detail_description` is overriden
|
|
detailed_description = None
|
|
|
|
# It does make sense to change context on creation
|
|
# - in some cases it may confuse artists because it would not be used
|
|
# e.g. for buld creators
|
|
create_allow_context_change = True
|
|
# A thumbnail can be passed in precreate attributes
|
|
# - if is set to True is should expect that a thumbnail path under key
|
|
# PRE_CREATE_THUMBNAIL_KEY can be sent in data with precreate data
|
|
# - is disabled by default because the feature was added in later stages
|
|
# and creators who would not expect PRE_CREATE_THUMBNAIL_KEY could
|
|
# cause issues with instance data
|
|
create_allow_thumbnail = False
|
|
|
|
# Precreate attribute definitions showed before creation
|
|
# - similar to instance attribute definitions
|
|
pre_create_attr_defs = []
|
|
|
|
@property
|
|
def show_order(self):
|
|
"""Order in which is creator shown in UI.
|
|
|
|
Returns:
|
|
int: Order in which is creator shown (less == earlier). By default
|
|
is using Creator's 'order' or processing.
|
|
"""
|
|
|
|
return self.order
|
|
|
|
@abstractmethod
|
|
def create(self, subset_name, instance_data, pre_create_data):
|
|
"""Create new instance and store it.
|
|
|
|
Ideally should be stored to workfile using host implementation.
|
|
|
|
Args:
|
|
subset_name(str): Subset name of created instance.
|
|
instance_data(dict): Base data for instance.
|
|
pre_create_data(dict): Data based on pre creation attributes.
|
|
Those may affect how creator works.
|
|
"""
|
|
|
|
# instance = CreatedInstance(
|
|
# self.family, subset_name, instance_data
|
|
# )
|
|
pass
|
|
|
|
def get_description(self):
|
|
"""Short description of family and plugin.
|
|
|
|
Returns:
|
|
str: Short description of family.
|
|
"""
|
|
|
|
return self.description
|
|
|
|
def get_detail_description(self):
|
|
"""Description of family and plugin.
|
|
|
|
Can be detailed with markdown or html tags.
|
|
|
|
Returns:
|
|
str: Detailed description of family for artist.
|
|
"""
|
|
|
|
return self.detailed_description
|
|
|
|
def get_default_variants(self):
|
|
"""Default variant values for UI tooltips.
|
|
|
|
Replacement of `defatults` attribute. Using method gives ability to
|
|
have some "logic" other than attribute values.
|
|
|
|
By default returns `default_variants` value.
|
|
|
|
Returns:
|
|
List[str]: Whisper variants for user input.
|
|
"""
|
|
|
|
return copy.deepcopy(self.default_variants)
|
|
|
|
def get_default_variant(self):
|
|
"""Default variant value that will be used to prefill variant input.
|
|
|
|
This is for user input and value may not be content of result from
|
|
`get_default_variants`.
|
|
|
|
Can return `None`. In that case first element from
|
|
`get_default_variants` should be used.
|
|
"""
|
|
|
|
return self.default_variant
|
|
|
|
def get_pre_create_attr_defs(self):
|
|
"""Plugin attribute definitions needed for creation.
|
|
Attribute definitions of plugin that define how creation will work.
|
|
Values of these definitions are passed to `create` method.
|
|
|
|
Note:
|
|
Convert method should be implemented which should care about
|
|
updating keys/values when plugin attributes change.
|
|
|
|
Returns:
|
|
List[AbstractAttrDef]: Attribute definitions that can be tweaked
|
|
for created instance.
|
|
"""
|
|
return self.pre_create_attr_defs
|
|
|
|
|
|
class HiddenCreator(BaseCreator):
|
|
@abstractmethod
|
|
def create(self, instance_data, source_data):
|
|
pass
|
|
|
|
|
|
class AutoCreator(BaseCreator):
|
|
"""Creator which is automatically triggered without user interaction.
|
|
|
|
Can be used e.g. for `workfile`.
|
|
"""
|
|
|
|
def remove_instances(self, instances):
|
|
"""Skip removement."""
|
|
pass
|
|
|
|
|
|
def discover_creator_plugins(*args, **kwargs):
|
|
return discover(BaseCreator, *args, **kwargs)
|
|
|
|
|
|
def discover_convertor_plugins(*args, **kwargs):
|
|
return discover(SubsetConvertorPlugin, *args, **kwargs)
|
|
|
|
|
|
def discover_legacy_creator_plugins():
|
|
from openpype.pipeline import get_current_project_name
|
|
|
|
log = Logger.get_logger("CreatorDiscover")
|
|
|
|
plugins = discover(LegacyCreator)
|
|
project_name = get_current_project_name()
|
|
system_settings = get_system_settings()
|
|
project_settings = get_project_settings(project_name)
|
|
for plugin in plugins:
|
|
try:
|
|
plugin.apply_settings(project_settings, system_settings)
|
|
except Exception:
|
|
log.warning(
|
|
"Failed to apply settings to creator {}".format(
|
|
plugin.__name__
|
|
),
|
|
exc_info=True
|
|
)
|
|
return plugins
|
|
|
|
|
|
def get_legacy_creator_by_name(creator_name, case_sensitive=False):
|
|
"""Find creator plugin by name.
|
|
|
|
Args:
|
|
creator_name (str): Name of creator class that should be returned.
|
|
case_sensitive (bool): Match of creator plugin name is case sensitive.
|
|
Set to `False` by default.
|
|
|
|
Returns:
|
|
Creator: Return first matching plugin or `None`.
|
|
"""
|
|
|
|
# Lower input creator name if is not case sensitive
|
|
if not case_sensitive:
|
|
creator_name = creator_name.lower()
|
|
|
|
for creator_plugin in discover_legacy_creator_plugins():
|
|
_creator_name = creator_plugin.__name__
|
|
|
|
# Lower creator plugin name if is not case sensitive
|
|
if not case_sensitive:
|
|
_creator_name = _creator_name.lower()
|
|
|
|
if _creator_name == creator_name:
|
|
return creator_plugin
|
|
return None
|
|
|
|
|
|
def register_creator_plugin(plugin):
|
|
if issubclass(plugin, BaseCreator):
|
|
register_plugin(BaseCreator, 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):
|
|
deregister_plugin(BaseCreator, 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)
|
|
|
|
|
|
def cache_and_get_instances(creator, shared_key, list_instances_func):
|
|
"""Common approach to cache instances in shared data.
|
|
|
|
This is helper function which does not handle cases when a 'shared_key' is
|
|
used for different list instances functions. The same approach of caching
|
|
instances into 'collection_shared_data' is not required but is so common
|
|
we've decided to unify it to some degree.
|
|
|
|
Function 'list_instances_func' is called only if 'shared_key' is not
|
|
available in 'collection_shared_data' on creator.
|
|
|
|
Args:
|
|
creator (Creator): Plugin which would like to get instance data.
|
|
shared_key (str): Key under which output of function will be stored.
|
|
list_instances_func (Function): Function that will return instance data
|
|
if data were not yet stored under 'shared_key'.
|
|
|
|
Returns:
|
|
Dict[str, Dict[str, Any]]: Cached instances by creator identifier from
|
|
result of passed function.
|
|
"""
|
|
|
|
if shared_key not in creator.collection_shared_data:
|
|
value = collections.defaultdict(list)
|
|
for instance in list_instances_func():
|
|
identifier = instance.get("creator_identifier")
|
|
value[identifier].append(instance)
|
|
creator.collection_shared_data[shared_key] = value
|
|
return creator.collection_shared_data[shared_key]
|