diff --git a/openpype/hosts/aftereffects/addon.py b/openpype/hosts/aftereffects/addon.py index 94843e7dc5..79df550312 100644 --- a/openpype/hosts/aftereffects/addon.py +++ b/openpype/hosts/aftereffects/addon.py @@ -1,5 +1,4 @@ -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon class AfterEffectsAddon(OpenPypeModule, IHostAddon): diff --git a/openpype/hosts/blender/addon.py b/openpype/hosts/blender/addon.py index 3ee638a5bb..f1da9b808c 100644 --- a/openpype/hosts/blender/addon.py +++ b/openpype/hosts/blender/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon BLENDER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/flame/addon.py b/openpype/hosts/flame/addon.py index 5a34413bb0..d9359fc5bf 100644 --- a/openpype/hosts/flame/addon.py +++ b/openpype/hosts/flame/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon HOST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py index 1913cc2e30..d1bd1566b7 100644 --- a/openpype/hosts/fusion/addon.py +++ b/openpype/hosts/fusion/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/harmony/addon.py b/openpype/hosts/harmony/addon.py index 872a7490b5..efef40ab92 100644 --- a/openpype/hosts/harmony/addon.py +++ b/openpype/hosts/harmony/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/hiero/addon.py b/openpype/hosts/hiero/addon.py index 3523e9aed7..f5bb94dbaa 100644 --- a/openpype/hosts/hiero/addon.py +++ b/openpype/hosts/hiero/addon.py @@ -1,7 +1,6 @@ import os import platform -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon HIERO_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/houdini/addon.py b/openpype/hosts/houdini/addon.py index 8d88e83c56..80856b0624 100644 --- a/openpype/hosts/houdini/addon.py +++ b/openpype/hosts/houdini/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon HOUDINI_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/maya/addon.py b/openpype/hosts/maya/addon.py index cdd2bc1667..b9ecb8279f 100644 --- a/openpype/hosts/maya/addon.py +++ b/openpype/hosts/maya/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon MAYA_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/nuke/addon.py b/openpype/hosts/nuke/addon.py index 54e4da5195..1c5d5c4005 100644 --- a/openpype/hosts/nuke/addon.py +++ b/openpype/hosts/nuke/addon.py @@ -1,7 +1,6 @@ import os import platform -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon NUKE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/photoshop/addon.py b/openpype/hosts/photoshop/addon.py index a41d91554b..965a545ac5 100644 --- a/openpype/hosts/photoshop/addon.py +++ b/openpype/hosts/photoshop/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon PHOTOSHOP_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/resolve/addon.py b/openpype/hosts/resolve/addon.py index a31da52a6d..02c1d7957f 100644 --- a/openpype/hosts/resolve/addon.py +++ b/openpype/hosts/resolve/addon.py @@ -1,7 +1,6 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon from .utils import RESOLVE_ROOT_DIR diff --git a/openpype/hosts/standalonepublisher/addon.py b/openpype/hosts/standalonepublisher/addon.py index 98ec44d4e2..65a4226664 100644 --- a/openpype/hosts/standalonepublisher/addon.py +++ b/openpype/hosts/standalonepublisher/addon.py @@ -4,8 +4,7 @@ import click from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import ITrayAction, IHostAddon +from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/traypublisher/addon.py b/openpype/hosts/traypublisher/addon.py index c86c835ed9..c157799898 100644 --- a/openpype/hosts/traypublisher/addon.py +++ b/openpype/hosts/traypublisher/addon.py @@ -4,8 +4,7 @@ import click from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import ITrayAction, IHostAddon +from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 40877968e9..75930f0f31 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,54 +1,32 @@ -import collections - from openpype.lib.attribute_definitions import FileDef +from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS from openpype.pipeline.create import ( Creator, HiddenCreator, CreatedInstance, + cache_and_get_instances, PRE_CREATE_THUMBNAIL_KEY, ) - from .pipeline import ( list_instances, update_instances, remove_instances, HostContext, ) -from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS - REVIEW_EXTENSIONS = set(IMAGE_EXTENSIONS) | set(VIDEO_EXTENSIONS) - - -def _cache_and_get_instances(creator): - """Cache instances in shared data. - - Args: - creator (Creator): Plugin which would like to get instances from host. - - Returns: - List[Dict[str, Any]]: Cached instances list from host implementation. - """ - - shared_key = "openpype.traypublisher.instances" - if shared_key not in creator.collection_shared_data: - instances_by_creator_id = collections.defaultdict(list) - for instance_data in list_instances(): - creator_id = instance_data.get("creator_identifier") - instances_by_creator_id[creator_id].append(instance_data) - creator.collection_shared_data[shared_key] = instances_by_creator_id - return creator.collection_shared_data[shared_key] +SHARED_DATA_KEY = "openpype.traypublisher.instances" class HiddenTrayPublishCreator(HiddenCreator): host_name = "traypublisher" def collect_instances(self): - instance_data_by_identifier = _cache_and_get_instances(self) - for instance_data in instance_data_by_identifier[self.identifier]: - instance = CreatedInstance.from_existing( - instance_data, self - ) + instances_by_identifier = cache_and_get_instances( + self, SHARED_DATA_KEY, list_instances + ) + for instance_data in instances_by_identifier[self.identifier]: + instance = CreatedInstance.from_existing(instance_data, self) self._add_instance_to_context(instance) def update_instances(self, update_list): @@ -80,11 +58,11 @@ class TrayPublishCreator(Creator): host_name = "traypublisher" def collect_instances(self): - instance_data_by_identifier = _cache_and_get_instances(self) - for instance_data in instance_data_by_identifier[self.identifier]: - instance = CreatedInstance.from_existing( - instance_data, self - ) + instances_by_identifier = cache_and_get_instances( + self, SHARED_DATA_KEY, list_instances + ) + for instance_data in instances_by_identifier[self.identifier]: + instance = CreatedInstance.from_existing(instance_data, self) self._add_instance_to_context(instance) def update_instances(self, update_list): diff --git a/openpype/hosts/tvpaint/addon.py b/openpype/hosts/tvpaint/addon.py index d710e63f93..b695bf8ecc 100644 --- a/openpype/hosts/tvpaint/addon.py +++ b/openpype/hosts/tvpaint/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon TVPAINT_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/tvpaint/plugins/load/load_image.py b/openpype/hosts/tvpaint/plugins/load/load_image.py index 151db94135..5283d04355 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_image.py @@ -1,4 +1,4 @@ -import qargparse +from openpype.lib.attribute_definitions import BoolDef from openpype.hosts.tvpaint.api import plugin from openpype.hosts.tvpaint.api.lib import execute_george_through_file @@ -27,26 +27,28 @@ class ImportImage(plugin.Loader): "preload": True } - options = [ - qargparse.Boolean( - "stretch", - label="Stretch to project size", - default=True, - help="Stretch loaded image/s to project resolution?" - ), - qargparse.Boolean( - "timestretch", - label="Stretch to timeline length", - default=True, - help="Clip loaded image/s to timeline length?" - ), - qargparse.Boolean( - "preload", - label="Preload loaded image/s", - default=True, - help="Preload image/s?" - ) - ] + @classmethod + def get_options(cls, contexts): + return [ + BoolDef( + "stretch", + label="Stretch to project size", + default=cls.defaults["stretch"], + tooltip="Stretch loaded image/s to project resolution?" + ), + BoolDef( + "timestretch", + label="Stretch to timeline length", + default=cls.defaults["timestretch"], + tooltip="Clip loaded image/s to timeline length?" + ), + BoolDef( + "preload", + label="Preload loaded image/s", + default=cls.defaults["preload"], + tooltip="Preload image/s?" + ) + ] def load(self, context, name, namespace, options): stretch = options.get("stretch", self.defaults["stretch"]) diff --git a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py index 393236fba6..7f7a68cc41 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py @@ -1,7 +1,6 @@ import collections -import qargparse - +from openpype.lib.attribute_definitions import BoolDef from openpype.pipeline import ( get_representation_context, register_host, @@ -42,26 +41,28 @@ class LoadImage(plugin.Loader): "preload": True } - options = [ - qargparse.Boolean( - "stretch", - label="Stretch to project size", - default=True, - help="Stretch loaded image/s to project resolution?" - ), - qargparse.Boolean( - "timestretch", - label="Stretch to timeline length", - default=True, - help="Clip loaded image/s to timeline length?" - ), - qargparse.Boolean( - "preload", - label="Preload loaded image/s", - default=True, - help="Preload image/s?" - ) - ] + @classmethod + def get_options(cls, contexts): + return [ + BoolDef( + "stretch", + label="Stretch to project size", + default=cls.defaults["stretch"], + tooltip="Stretch loaded image/s to project resolution?" + ), + BoolDef( + "timestretch", + label="Stretch to timeline length", + default=cls.defaults["timestretch"], + tooltip="Clip loaded image/s to timeline length?" + ), + BoolDef( + "preload", + label="Preload loaded image/s", + default=cls.defaults["preload"], + tooltip="Preload image/s?" + ) + ] def load(self, context, name, namespace, options): stretch = options.get("stretch", self.defaults["stretch"]) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index 16736214c5..e2c8484651 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/webpublisher/addon.py b/openpype/hosts/webpublisher/addon.py index a64d74e62b..eb7fced2e6 100644 --- a/openpype/hosts/webpublisher/addon.py +++ b/openpype/hosts/webpublisher/addon.py @@ -2,8 +2,7 @@ import os import click -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostAddon +from openpype.modules import OpenPypeModule, IHostAddon WEBPUBLISHER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index dd4646f356..2bf097de41 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -83,8 +83,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): self.log.info("task_data:: {}".format(task_data)) is_sequence = len(task_data["files"]) > 1 + first_file = task_data["files"][0] - _, extension = os.path.splitext(task_data["files"][0]) + _, extension = os.path.splitext(first_file) family, families, tags = self._get_family( self.task_type_to_family, task_type, @@ -149,10 +150,13 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): self.log.warning("Unable to count frames " "duration {}".format(no_of_frames)) - # raise ValueError("STOP") instance.data["handleStart"] = asset_doc["data"]["handleStart"] instance.data["handleEnd"] = asset_doc["data"]["handleEnd"] + if "review" in tags: + first_file_path = os.path.join(task_dir, first_file) + instance.data["thumbnailSource"] = first_file_path + instances.append(instance) self.log.info("instance.data:: {}".format(instance.data)) diff --git a/openpype/hosts/webpublisher/plugins/publish/extract_thumbnail.py b/openpype/hosts/webpublisher/plugins/publish/extract_thumbnail.py deleted file mode 100644 index a56521891b..0000000000 --- a/openpype/hosts/webpublisher/plugins/publish/extract_thumbnail.py +++ /dev/null @@ -1,137 +0,0 @@ -import os -import shutil - -import pyblish.api -from openpype.lib import ( - get_ffmpeg_tool_path, - - run_subprocess, - - get_transcode_temp_directory, - convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg -) - - -class ExtractThumbnail(pyblish.api.InstancePlugin): - """Create jpg thumbnail from input using ffmpeg.""" - - label = "Extract Thumbnail" - order = pyblish.api.ExtractorOrder - families = [ - "render", - "image" - ] - hosts = ["webpublisher"] - targets = ["filespublish"] - - def process(self, instance): - self.log.info("subset {}".format(instance.data['subset'])) - - filtered_repres = self._get_filtered_repres(instance) - for repre in filtered_repres: - repre_files = repre["files"] - if not isinstance(repre_files, (list, tuple)): - input_file = repre_files - else: - file_index = int(float(len(repre_files)) * 0.5) - input_file = repre_files[file_index] - - stagingdir = os.path.normpath(repre["stagingDir"]) - - full_input_path = os.path.join(stagingdir, input_file) - self.log.info("Input filepath: {}".format(full_input_path)) - - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - while filename.endswith("."): - filename = filename[:-1] - thumbnail_filename = filename + "_thumbnail.jpg" - full_output_path = os.path.join(stagingdir, thumbnail_filename) - - self.log.info("output {}".format(full_output_path)) - - ffmpeg_args = [ - get_ffmpeg_tool_path("ffmpeg"), - "-y", - "-i", full_input_path, - "-vframes", "1", - full_output_path - ] - - # run subprocess - self.log.debug("{}".format(" ".join(ffmpeg_args))) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - ffmpeg_args, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!!!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise - - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": thumbnail_filename, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) - - def _get_filtered_repres(self, instance): - filtered_repres = [] - repres = instance.data.get("representations") or [] - for repre in repres: - self.log.debug(repre) - tags = repre.get("tags") or [] - # Skip instance if already has thumbnail representation - if "thumbnail" in tags: - return [] - - if "review" not in tags: - continue - - if not repre.get("files"): - self.log.info(( - "Representation \"{}\" don't have files. Skipping" - ).format(repre["name"])) - continue - - filtered_repres.append(repre) - return filtered_repres diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index bb0b07948f..589a4ef9ab 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -91,7 +91,7 @@ class AbstractAttrDefMeta(ABCMeta): @six.add_metaclass(AbstractAttrDefMeta) -class AbtractAttrDef: +class AbtractAttrDef(object): """Abstraction of attribute definiton. Each attribute definition must have implemented validation and diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 02e7dc13ab..1f345feea9 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -1,4 +1,14 @@ # -*- coding: utf-8 -*- +from .interfaces import ( + ILaunchHookPaths, + IPluginPaths, + ITrayModule, + ITrayAction, + ITrayService, + ISettingsChangeListener, + IHostAddon, +) + from .base import ( OpenPypeModule, OpenPypeAddOn, @@ -17,6 +27,14 @@ from .base import ( __all__ = ( + "ILaunchHookPaths", + "IPluginPaths", + "ITrayModule", + "ITrayAction", + "ITrayService", + "ISettingsChangeListener", + "IHostAddon", + "OpenPypeModule", "OpenPypeAddOn", diff --git a/openpype/modules/avalon_apps/avalon_app.py b/openpype/modules/avalon_apps/avalon_app.py index 1d21de129b..f9085522b0 100644 --- a/openpype/modules/avalon_apps/avalon_app.py +++ b/openpype/modules/avalon_apps/avalon_app.py @@ -1,7 +1,6 @@ import os -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayModule +from openpype.modules import OpenPypeModule, ITrayModule class AvalonModule(OpenPypeModule, ITrayModule): diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 09aea50424..4761462df0 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -9,6 +9,7 @@ import logging import platform import threading import collections +import traceback from uuid import uuid4 from abc import ABCMeta, abstractmethod import six @@ -139,6 +140,15 @@ class _InterfacesClass(_ModuleClass): "cannot import name '{}' from 'openpype_interfaces'" ).format(attr_name)) + if _LoadCache.interfaces_loaded and attr_name != "log": + stack = list(traceback.extract_stack()) + stack.pop(-1) + self.log.warning(( + "Using deprecated import of \"{}\" from 'openpype_interfaces'." + " Please switch to use import" + " from 'openpype.modules.interfaces'" + " (will be removed after 3.16.x).{}" + ).format(attr_name, "".join(traceback.format_list(stack)))) return self.__attributes__[attr_name] diff --git a/openpype/modules/clockify/clockify_module.py b/openpype/modules/clockify/clockify_module.py index 932ce87c36..14fcb01f67 100644 --- a/openpype/modules/clockify/clockify_module.py +++ b/openpype/modules/clockify/clockify_module.py @@ -2,16 +2,17 @@ import os import threading import time +from openpype.modules import ( + OpenPypeModule, + ITrayModule, + IPluginPaths +) + from .clockify_api import ClockifyAPI from .constants import ( CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH ) -from openpype.modules import OpenPypeModule -from openpype_interfaces import ( - ITrayModule, - IPluginPaths -) class ClockifyModule( diff --git a/openpype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py index bbd0f74e8a..9855f8c1b1 100644 --- a/openpype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -4,8 +4,7 @@ import six import sys from openpype.lib import requests_get, Logger -from openpype.modules import OpenPypeModule -from openpype_interfaces import IPluginPaths +from openpype.modules import OpenPypeModule, IPluginPaths class DeadlineWebserviceError(Exception): diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index 50554b1e43..ead647b41d 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -13,10 +13,7 @@ import click from openpype.modules import ( JsonFilesSettingsDef, OpenPypeAddOn, - ModulesManager -) -# Import interface defined by this addon to be able find other addons using it -from openpype_interfaces import ( + ModulesManager, IPluginPaths, ITrayAction ) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 678af0e577..6f14f8428d 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -5,8 +5,8 @@ import platform import click -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import ( +from openpype.modules import ( + OpenPypeModule, ITrayModule, IPluginPaths, ISettingsChangeListener diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 23c032715b..b91373af20 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -3,8 +3,11 @@ import click import os -from openpype.modules import OpenPypeModule -from openpype_interfaces import IPluginPaths, ITrayAction +from openpype.modules import ( + OpenPypeModule, + IPluginPaths, + ITrayAction, +) class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): diff --git a/openpype/modules/launcher_action.py b/openpype/modules/launcher_action.py index e3252e3842..c4331b6094 100644 --- a/openpype/modules/launcher_action.py +++ b/openpype/modules/launcher_action.py @@ -1,5 +1,7 @@ -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules import ( + OpenPypeModule, + ITrayAction, +) class LauncherAction(OpenPypeModule, ITrayAction): diff --git a/openpype/modules/log_viewer/log_view_module.py b/openpype/modules/log_viewer/log_view_module.py index da1628b71f..31e954fadd 100644 --- a/openpype/modules/log_viewer/log_view_module.py +++ b/openpype/modules/log_viewer/log_view_module.py @@ -1,5 +1,4 @@ -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayModule +from openpype.modules import OpenPypeModule, ITrayModule class LogViewModule(OpenPypeModule, ITrayModule): diff --git a/openpype/modules/muster/muster.py b/openpype/modules/muster/muster.py index 6e26ad2d7b..8d395d16e8 100644 --- a/openpype/modules/muster/muster.py +++ b/openpype/modules/muster/muster.py @@ -2,8 +2,7 @@ import os import json import appdirs import requests -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayModule +from openpype.modules import OpenPypeModule, ITrayModule class MusterModule(OpenPypeModule, ITrayModule): diff --git a/openpype/modules/project_manager_action.py b/openpype/modules/project_manager_action.py index 251964a059..5f74dd9ee5 100644 --- a/openpype/modules/project_manager_action.py +++ b/openpype/modules/project_manager_action.py @@ -1,5 +1,4 @@ -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules import OpenPypeModule, ITrayAction class ProjectManagerAction(OpenPypeModule, ITrayAction): diff --git a/openpype/modules/python_console_interpreter/module.py b/openpype/modules/python_console_interpreter/module.py index 8c4a2fba73..cb99c05e37 100644 --- a/openpype/modules/python_console_interpreter/module.py +++ b/openpype/modules/python_console_interpreter/module.py @@ -1,5 +1,4 @@ -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules import OpenPypeModule, ITrayAction class PythonInterpreterAction(OpenPypeModule, ITrayAction): diff --git a/openpype/modules/royalrender/royal_render_module.py b/openpype/modules/royalrender/royal_render_module.py index 4f72860ad6..10d74d01d1 100644 --- a/openpype/modules/royalrender/royal_render_module.py +++ b/openpype/modules/royalrender/royal_render_module.py @@ -2,8 +2,7 @@ """Module providing support for Royal Render.""" import os import openpype.modules -from openpype.modules import OpenPypeModule -from openpype_interfaces import IPluginPaths +from openpype.modules import OpenPypeModule, IPluginPaths class RoyalRenderModule(OpenPypeModule, IPluginPaths): diff --git a/openpype/modules/settings_action.py b/openpype/modules/settings_action.py index 1e7eca4dec..1902caff1d 100644 --- a/openpype/modules/settings_action.py +++ b/openpype/modules/settings_action.py @@ -1,5 +1,4 @@ -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules import OpenPypeModule, ITrayAction class SettingsAction(OpenPypeModule, ITrayAction): diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 281c6fdcad..d26647d06a 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,12 +1,11 @@ import os -from openpype_interfaces import ( +from openpype.modules import ( + OpenPypeModule, ITrayModule, IPluginPaths, ) -from openpype.modules import OpenPypeModule - SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/modules/slack/slack_module.py b/openpype/modules/slack/slack_module.py index 499c1c19ce..797ae19f4a 100644 --- a/openpype/modules/slack/slack_module.py +++ b/openpype/modules/slack/slack_module.py @@ -1,6 +1,5 @@ import os -from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IPluginPaths +from openpype.modules import OpenPypeModule, IPluginPaths SLACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index a478faa9ef..e84c333a58 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -11,9 +11,12 @@ from collections import deque, defaultdict import click from bson.objectid import ObjectId -from openpype.client import get_projects -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayModule +from openpype.client import ( + get_projects, + get_representations, + get_representation_by_id, +) +from openpype.modules import OpenPypeModule, ITrayModule from openpype.settings import ( get_project_settings, get_system_settings, @@ -30,9 +33,6 @@ from .providers import lib from .utils import time_function, SyncStatus, SiteAlreadyPresentError -from openpype.client import get_representations, get_representation_by_id - - log = Logger.get_logger("SyncServer") diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index c168e9534d..0ba68285a4 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -3,8 +3,8 @@ import platform from openpype.client import get_asset_by_name -from openpype.modules import OpenPypeModule -from openpype_interfaces import ( +from openpype.modules import ( + OpenPypeModule, ITrayService, IPluginPaths ) diff --git a/openpype/modules/webserver/host_console_listener.py b/openpype/modules/webserver/host_console_listener.py index 6138f9f097..fdfe1ba688 100644 --- a/openpype/modules/webserver/host_console_listener.py +++ b/openpype/modules/webserver/host_console_listener.py @@ -5,7 +5,7 @@ import logging from concurrent.futures import CancelledError from Qt import QtWidgets -from openpype_interfaces import ITrayService +from openpype.modules import ITrayService log = logging.getLogger(__name__) diff --git a/openpype/modules/webserver/webserver_module.py b/openpype/modules/webserver/webserver_module.py index 16861abd29..354ab1e4f9 100644 --- a/openpype/modules/webserver/webserver_module.py +++ b/openpype/modules/webserver/webserver_module.py @@ -24,8 +24,7 @@ import os import socket from openpype import resources -from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayService +from openpype.modules import OpenPypeModule, ITrayService class WebServerModule(OpenPypeModule, ITrayService): diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 89b876e6de..b0877d0a29 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -25,6 +25,8 @@ from .creator_plugins import ( deregister_creator_plugin, register_creator_plugin_path, deregister_creator_plugin_path, + + cache_and_get_instances, ) from .context import ( diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index ef92b7ccc4..782534d589 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -1,5 +1,6 @@ import os import copy +import collections from abc import ( ABCMeta, @@ -674,3 +675,34 @@ 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] diff --git a/openpype/plugins/publish/preintegrate_thumbnail_representation.py b/openpype/plugins/publish/preintegrate_thumbnail_representation.py index f9e23223e6..b88ccee9dc 100644 --- a/openpype/plugins/publish/preintegrate_thumbnail_representation.py +++ b/openpype/plugins/publish/preintegrate_thumbnail_representation.py @@ -21,9 +21,8 @@ class PreIntegrateThumbnails(pyblish.api.InstancePlugin): label = "Override Integrate Thumbnail Representations" order = pyblish.api.IntegratorOrder - 0.1 - families = ["review"] - integrate_profiles = {} + integrate_profiles = [] def process(self, instance): repres = instance.data.get("representations") diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 5eaddf6e6e..288c587d03 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -138,8 +138,7 @@ def save_studio_settings(data): SaveWarningExc: If any module raises the exception. """ # Notify Pype modules - from openpype.modules import ModulesManager - from openpype_interfaces import ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener old_data = get_system_settings() default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] @@ -186,8 +185,7 @@ def save_project_settings(project_name, overrides): SaveWarningExc: If any module raises the exception. """ # Notify Pype modules - from openpype.modules import ModulesManager - from openpype_interfaces import ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener default_values = get_default_settings()[PROJECT_SETTINGS_KEY] if project_name: @@ -248,8 +246,7 @@ def save_project_anatomy(project_name, anatomy_data): SaveWarningExc: If any module raises the exception. """ # Notify Pype modules - from openpype.modules import ModulesManager - from openpype_interfaces import ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener default_values = get_default_settings()[PROJECT_ANATOMY_KEY] if project_name: diff --git a/openpype/widgets/attribute_defs/__init__.py b/openpype/tools/attribute_defs/__init__.py similarity index 65% rename from openpype/widgets/attribute_defs/__init__.py rename to openpype/tools/attribute_defs/__init__.py index ce6b80109e..f991fdec3d 100644 --- a/openpype/widgets/attribute_defs/__init__.py +++ b/openpype/tools/attribute_defs/__init__.py @@ -3,8 +3,14 @@ from .widgets import ( AttributeDefinitionsWidget, ) +from .dialog import ( + AttributeDefinitionsDialog, +) + __all__ = ( "create_widget_for_attr_def", "AttributeDefinitionsWidget", + + "AttributeDefinitionsDialog", ) diff --git a/openpype/tools/attribute_defs/dialog.py b/openpype/tools/attribute_defs/dialog.py new file mode 100644 index 0000000000..69923d54e5 --- /dev/null +++ b/openpype/tools/attribute_defs/dialog.py @@ -0,0 +1,33 @@ +from Qt import QtWidgets + +from .widgets import AttributeDefinitionsWidget + + +class AttributeDefinitionsDialog(QtWidgets.QDialog): + def __init__(self, attr_defs, parent=None): + super(AttributeDefinitionsDialog, self).__init__(parent) + + attrs_widget = AttributeDefinitionsWidget(attr_defs, self) + + btns_widget = QtWidgets.QWidget(self) + ok_btn = QtWidgets.QPushButton("OK", btns_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) + + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.setContentsMargins(0, 0, 0, 0) + btns_layout.addStretch(1) + btns_layout.addWidget(ok_btn, 0) + btns_layout.addWidget(cancel_btn, 0) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(attrs_widget, 0) + main_layout.addStretch(1) + main_layout.addWidget(btns_widget, 0) + + ok_btn.clicked.connect(self.accept) + cancel_btn.clicked.connect(self.reject) + + self._attrs_widget = attrs_widget + + def get_values(self): + return self._attrs_widget.current_value() diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/tools/attribute_defs/files_widget.py similarity index 100% rename from openpype/widgets/attribute_defs/files_widget.py rename to openpype/tools/attribute_defs/files_widget.py diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py similarity index 100% rename from openpype/widgets/attribute_defs/widgets.py rename to openpype/tools/attribute_defs/widgets.py diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py index 28e94237ec..78a25d8d85 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -2,6 +2,8 @@ import inspect from Qt import QtGui import qtawesome +from openpype.lib.attribute_definitions import AbtractAttrDef +from openpype.tools.attribute_defs import AttributeDefinitionsDialog from openpype.tools.utils.widgets import ( OptionalAction, OptionDialog @@ -34,21 +36,30 @@ def get_options(action, loader, parent, repre_contexts): None when dialog was closed or cancelled, in all other cases {} if no options """ + # Pop option dialog options = {} loader_options = loader.get_options(repre_contexts) - if getattr(action, "optioned", False) and loader_options: + if not getattr(action, "optioned", False) or not loader_options: + return options + + if isinstance(loader_options[0], AbtractAttrDef): + qargparse_options = False + dialog = AttributeDefinitionsDialog(loader_options, parent) + else: + qargparse_options = True dialog = OptionDialog(parent) - dialog.setWindowTitle(action.label + " Options") dialog.create(loader_options) - if not dialog.exec_(): - return None + dialog.setWindowTitle(action.label + " Options") - # Get option - options = dialog.parse() + if not dialog.exec_(): + return None - return options + # Get option + if qargparse_options: + return dialog.parse() + return dialog.get_values() def add_representation_loaders_to_menu(loaders, menu, repre_contexts): diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 10734a69f4..615f3eb8d9 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -90,9 +90,9 @@ class AssetDocsCache: return project_name = self._controller.project_name - asset_docs = get_assets( + asset_docs = list(get_assets( project_name, fields=self.projection.keys() - ) + )) asset_docs_by_name = {} task_names_by_asset_name = {} for asset_doc in asset_docs: diff --git a/openpype/tools/publisher/widgets/precreate_widget.py b/openpype/tools/publisher/widgets/precreate_widget.py index ef34c9bcb5..b688a83053 100644 --- a/openpype/tools/publisher/widgets/precreate_widget.py +++ b/openpype/tools/publisher/widgets/precreate_widget.py @@ -1,6 +1,6 @@ from Qt import QtWidgets, QtCore -from openpype.widgets.attribute_defs import create_widget_for_attr_def +from openpype.tools.attribute_defs import create_widget_for_attr_def class PreCreateWidget(QtWidgets.QWidget): diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index f0c1a6df80..447fd7bc12 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -9,7 +9,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome -from openpype.widgets.attribute_defs import create_widget_for_attr_def +from openpype.tools.attribute_defs import create_widget_for_attr_def from openpype.tools import resources from openpype.tools.flickcharm import FlickCharm from openpype.tools.utils import ( @@ -1229,7 +1229,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): Attributes are defined on creator so are dynamic. Their look and type is based on attribute definitions that are defined in `~/openpype/pipeline/lib/attribute_definitions.py` and their widget - representation in `~/openpype/widgets/attribute_defs/*`. + representation in `~/openpype/tools/attribute_defs/*`. Widgets are disabled if context of instance is not valid. @@ -1353,7 +1353,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): Look and type of attributes is based on attribute definitions that are defined in `~/openpype/pipeline/lib/attribute_definitions.py` and their - widget representation in `~/openpype/widgets/attribute_defs/*`. + widget representation in `~/openpype/tools/attribute_defs/*`. Widgets are disabled if context of instance is not valid. diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index f4b2c13a12..e1b3943317 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -892,6 +892,10 @@ class ProjectWidget(SettingsCategoryWidget): def __init__(self, *args, **kwargs): super(ProjectWidget, self).__init__(*args, **kwargs) + def set_edit_mode(self, enabled): + super(ProjectWidget, self).set_edit_mode(enabled) + self.project_list_widget.set_edit_mode(enabled) + def _check_last_saved_info(self): if self.is_modifying_defaults: return True diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index 722717df89..b8ad21e7e4 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -646,6 +646,9 @@ class UnsavedChangesDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(UnsavedChangesDialog, self).__init__(parent) + + self.setWindowTitle("Unsaved changes") + message_label = QtWidgets.QLabel(self.message) btns_widget = QtWidgets.QWidget(self) @@ -1009,6 +1012,7 @@ class ProjectListWidget(QtWidgets.QWidget): self._entity = None self.current_project = None + self._edit_mode = True super(ProjectListWidget, self).__init__(parent) self.setObjectName("ProjectListWidget") @@ -1061,6 +1065,10 @@ class ProjectListWidget(QtWidgets.QWidget): self.project_model = project_model self.inactive_chk = inactive_chk + def set_edit_mode(self, enabled): + if self._edit_mode is not enabled: + self._edit_mode = enabled + def set_entity(self, entity): self._entity = entity @@ -1112,7 +1120,7 @@ class ProjectListWidget(QtWidgets.QWidget): save_changes = False change_project = False - if self.validate_context_change(): + if not self._edit_mode or self.validate_context_change(): change_project = True else: diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 3842a4e216..d4189af4d8 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -401,7 +401,7 @@ class TrayManager: def initialize_modules(self): """Add modules to tray.""" - from openpype_interfaces import ( + from openpype.modules import ( ITrayAction, ITrayService ) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index 13225081ed..88893a57d5 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -3,10 +3,12 @@ import logging from Qt import QtWidgets, QtCore, QtGui import qargparse import qtawesome + from openpype.style import ( get_objected_colors, get_style_image_path ) +from openpype.lib.attribute_definitions import AbtractAttrDef log = logging.getLogger(__name__) @@ -401,8 +403,26 @@ class OptionalAction(QtWidgets.QWidgetAction): def set_option_tip(self, options): sep = "\n\n" - mak = (lambda opt: opt["name"] + " :\n " + opt["help"]) - self.option_tip = sep.join(mak(opt) for opt in options) + if not options or not isinstance(options[0], AbtractAttrDef): + mak = (lambda opt: opt["name"] + " :\n " + opt["help"]) + self.option_tip = sep.join(mak(opt) for opt in options) + return + + option_items = [] + for option in options: + option_lines = [] + if option.label: + option_lines.append( + "{} ({}) :".format(option.label, option.key) + ) + else: + option_lines.append("{} :".format(option.key)) + + if option.tooltip: + option_lines.append(" - {}".format(option.tooltip)) + option_items.append("\n".join(option_lines)) + + self.option_tip = sep.join(option_items) def on_option(self): self.optioned = True diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py index ea4e2fec5a..22e26be451 100644 --- a/openpype/tools/workfile_template_build/window.py +++ b/openpype/tools/workfile_template_build/window.py @@ -3,7 +3,7 @@ from Qt import QtWidgets from openpype import style from openpype.lib import Logger from openpype.pipeline import legacy_io -from openpype.widgets.attribute_defs import AttributeDefinitionsWidget +from openpype.tools.attribute_defs import AttributeDefinitionsWidget class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog):