diff --git a/.gitignore b/.gitignore index 41389755f1..4e56d77392 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ dump.sql mypy.ini .github_changelog_generator + +# ignore mkdocs build +site/ diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index d7cd3ba7f5..6f89a6d17d 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -252,7 +252,6 @@ def _set_global_environments() -> None: os.environ.update(env) # Hardcoded default values - os.environ["PYBLISH_GUI"] = "pyblish_pype" # Change scale factor only if is not set if "QT_AUTO_SCREEN_SCALE_FACTOR" not in os.environ: os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" @@ -290,8 +289,6 @@ def main(*args, **kwargs): split_paths = python_path.split(os.pathsep) additional_paths = [ - # add AYON tools for 'pyblish_pype' - os.path.join(AYON_CORE_ROOT, "tools"), # add common AYON vendor # (common for multiple Python interpreter versions) os.path.join(AYON_CORE_ROOT, "vendor", "python") diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 03ed574081..92c3966e77 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -9,6 +9,7 @@ from .local_settings import ( AYONSettingsRegistry, get_launcher_local_dir, get_launcher_storage_dir, + get_addons_resources_dir, get_local_site_id, get_ayon_username, ) @@ -142,6 +143,7 @@ __all__ = [ "AYONSettingsRegistry", "get_launcher_local_dir", "get_launcher_storage_dir", + "get_addons_resources_dir", "get_local_site_id", "get_ayon_username", diff --git a/client/ayon_core/lib/local_settings.py b/client/ayon_core/lib/local_settings.py index eff0068f00..d994145d4b 100644 --- a/client/ayon_core/lib/local_settings.py +++ b/client/ayon_core/lib/local_settings.py @@ -96,6 +96,30 @@ def get_launcher_local_dir(*subdirs: str) -> str: return os.path.join(storage_dir, *subdirs) +def get_addons_resources_dir(addon_name: str, *args) -> str: + """Get directory for storing resources for addons. + + Some addons might need to store ad-hoc resources that are not part of + addon client package (e.g. because of size). Studio might define + dedicated directory to store them with 'AYON_ADDONS_RESOURCES_DIR' + environment variable. By default, is used 'addons_resources' in + launcher storage (might be shared across platforms). + + Args: + addon_name (str): Addon name. + *args (str): Subfolders in resources directory. + + Returns: + str: Path to resources directory. + + """ + addons_resources_dir = os.getenv("AYON_ADDONS_RESOURCES_DIR") + if not addons_resources_dir: + addons_resources_dir = get_launcher_storage_dir("addons_resources") + + return os.path.join(addons_resources_dir, addon_name, *args) + + class AYONSecureRegistry: """Store information using keyring. diff --git a/client/ayon_core/lib/python_2_comp.py b/client/ayon_core/lib/python_2_comp.py deleted file mode 100644 index 900db59062..0000000000 --- a/client/ayon_core/lib/python_2_comp.py +++ /dev/null @@ -1,17 +0,0 @@ -# Deprecated file -# - the file container 'WeakMethod' implementation for Python 2 which is not -# needed anymore. -import warnings -import weakref - - -WeakMethod = weakref.WeakMethod - -warnings.warn( - ( - "'ayon_core.lib.python_2_comp' is deprecated." - "Please use 'weakref.WeakMethod'." - ), - DeprecationWarning, - stacklevel=2 -) diff --git a/client/ayon_core/lib/python_module_tools.py b/client/ayon_core/lib/python_module_tools.py index d146e069a9..a6dc8031c7 100644 --- a/client/ayon_core/lib/python_module_tools.py +++ b/client/ayon_core/lib/python_module_tools.py @@ -1,6 +1,8 @@ +"""Tools for working with python modules and classes.""" import os import sys import types +from typing import Optional import importlib import inspect import logging @@ -8,13 +10,22 @@ import logging log = logging.getLogger(__name__) -def import_filepath(filepath, module_name=None): +def import_filepath( + filepath: str, + module_name: Optional[str] = None, + sys_module_name: Optional[str] = None) -> types.ModuleType: """Import python file as python module. Args: filepath (str): Path to python file. module_name (str): Name of loaded module. Only for Python 3. By default is filled with filename of filepath. + sys_module_name (str): Name of module in `sys.modules` where to store + loaded module. By default is None so module is not added to + `sys.modules`. + + Todo (antirotor): We should add the module to the sys.modules always but + we need to be careful about it and test it properly. """ if module_name is None: @@ -28,6 +39,9 @@ def import_filepath(filepath, module_name=None): module_loader = importlib.machinery.SourceFileLoader( module_name, filepath ) + # only add to sys.modules if requested + if sys_module_name: + sys.modules[sys_module_name] = module module_loader.exec_module(module) return module @@ -126,7 +140,8 @@ def classes_from_module(superclass, module): return classes -def import_module_from_dirpath(dirpath, folder_name, dst_module_name=None): +def import_module_from_dirpath( + dirpath, folder_name, dst_module_name=None): """Import passed directory as a python module. Imported module can be assigned as a child attribute of already loaded @@ -193,7 +208,7 @@ def is_func_signature_supported(func, *args, **kwargs): Notes: This does NOT check if the function would work with passed arguments only if they can be passed in. If function have *args, **kwargs - in paramaters, this will always return 'True'. + in parameters, this will always return 'True'. Example: >>> def my_function(my_number): diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 24557234f4..26b04ed3ed 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -839,7 +839,7 @@ class CreateContext: publish_attributes.update(output) for plugin in self.plugins_with_defs: - attr_defs = plugin.get_attr_defs_for_context (self) + attr_defs = plugin.get_attr_defs_for_context(self) if not attr_defs: continue self._publish_attributes.set_publish_plugin_attr_defs( @@ -1259,50 +1259,6 @@ class CreateContext: with self._bulk_context("add", sender) as bulk_info: yield bulk_info - # Set publish attributes before bulk context is exited - for instance in bulk_info.get_data(): - publish_attributes = instance.publish_attributes - # Prepare publish plugin attributes and set it on instance - for plugin in self.plugins_with_defs: - try: - if is_func_signature_supported( - plugin.convert_attribute_values, self, instance - ): - plugin.convert_attribute_values(self, instance) - - elif plugin.__instanceEnabled__: - output = plugin.convert_attribute_values( - publish_attributes - ) - if output: - publish_attributes.update(output) - - except Exception: - self.log.error( - "Failed to convert attribute values of" - f" plugin '{plugin.__name__}'", - exc_info=True - ) - - for plugin in self.plugins_with_defs: - attr_defs = None - try: - attr_defs = plugin.get_attr_defs_for_instance( - self, instance - ) - except Exception: - self.log.error( - "Failed to get attribute definitions" - f" from plugin '{plugin.__name__}'.", - exc_info=True - ) - - if not attr_defs: - continue - instance.set_publish_plugin_attr_defs( - plugin.__name__, attr_defs - ) - @contextmanager def bulk_instances_collection(self, sender=None): """DEPRECATED use 'bulk_add_instances' instead.""" @@ -2251,6 +2207,50 @@ class CreateContext: if not instances_to_validate: return + # Set publish attributes before bulk callbacks are triggered + for instance in instances_to_validate: + publish_attributes = instance.publish_attributes + # Prepare publish plugin attributes and set it on instance + for plugin in self.plugins_with_defs: + try: + if is_func_signature_supported( + plugin.convert_attribute_values, self, instance + ): + plugin.convert_attribute_values(self, instance) + + elif plugin.__instanceEnabled__: + output = plugin.convert_attribute_values( + publish_attributes + ) + if output: + publish_attributes.update(output) + + except Exception: + self.log.error( + "Failed to convert attribute values of" + f" plugin '{plugin.__name__}'", + exc_info=True + ) + + for plugin in self.plugins_with_defs: + attr_defs = None + try: + attr_defs = plugin.get_attr_defs_for_instance( + self, instance + ) + except Exception: + self.log.error( + "Failed to get attribute definitions" + f" from plugin '{plugin.__name__}'.", + exc_info=True + ) + + if not attr_defs: + continue + instance.set_publish_plugin_attr_defs( + plugin.__name__, attr_defs + ) + # Cache folder and task entities for all instances at once self.get_instances_context_info(instances_to_validate) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index e48d99602e..c6f3ae7115 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -1,3 +1,4 @@ +from __future__ import annotations import copy import os import re @@ -247,7 +248,8 @@ def create_skeleton_instance( "useSequenceForReview": data.get("useSequenceForReview", True), # map inputVersions `ObjectId` -> `str` so json supports it "inputVersions": list(map(str, data.get("inputVersions", []))), - "colorspace": data.get("colorspace") + "colorspace": data.get("colorspace"), + "hasExplicitFrames": data.get("hasExplicitFrames") } if data.get("renderlayer"): @@ -324,8 +326,8 @@ def prepare_representations( skip_integration_repre_list (list): exclude specific extensions, do_not_add_review (bool): explicitly skip review color_managed_plugin (publish.ColormanagedPyblishPluginMixin) - frames_to_render (str): implicit or explicit range of frames to render - this value is sent to Deadline in JobInfo.Frames + frames_to_render (str | None): implicit or explicit range of frames + to render this value is sent to Deadline in JobInfo.Frames Returns: list of representations @@ -337,7 +339,7 @@ def prepare_representations( log = Logger.get_logger("farm_publishing") if frames_to_render is not None: - frames_to_render = _get_real_frames_to_render(frames_to_render) + frames_to_render = convert_frames_str_to_list(frames_to_render) else: # Backwards compatibility for older logic frame_start = int(skeleton_data.get("frameStartHandle")) @@ -386,17 +388,21 @@ def prepare_representations( frame_start -= 1 frames_to_render.insert(0, frame_start) - files = _get_real_files_to_render(collection, frames_to_render) + filenames = [ + os.path.basename(filepath) + for filepath in _get_real_files_to_render( + collection, frames_to_render + ) + ] # explicitly disable review by user preview = preview and not do_not_add_review rep = { "name": ext, "ext": ext, - "files": files, + "files": filenames, + "stagingDir": staging, "frameStart": frame_start, "frameEnd": frame_end, - # If expectedFile are absolute, we need only filenames - "stagingDir": staging, "fps": skeleton_data.get("fps"), "tags": ["review"] if preview else [], } @@ -475,21 +481,45 @@ def prepare_representations( return representations -def _get_real_frames_to_render(frames): - """Returns list of frames that should be rendered. +def convert_frames_str_to_list(frames: str) -> list[int]: + """Convert frames definition string to frames. + + Handles formats as: + >>> convert_frames_str_to_list('1001') + [1001] + >>> convert_frames_str_to_list('1002,1004') + [1002, 1004] + >>> convert_frames_str_to_list('1003-1005') + [1003, 1004, 1005] + >>> convert_frames_str_to_list('1001-1021x5') + [1001, 1006, 1011, 1016, 1021] + + Args: + frames (str): String with frames definition. + + Returns: + list[int]: List of frames. - Artists could want to selectively render only particular frames """ - frames_to_render = [] + step_pattern = re.compile(r"(?:step|by|every|x|:)(\d+)$") + + output = [] + step = 1 for frame in frames.split(","): if "-" in frame: - splitted = frame.split("-") - frames_to_render.extend( - range(int(splitted[0]), int(splitted[1])+1)) + frame_start, frame_end = frame.split("-") + match = step_pattern.findall(frame_end) + if match: + step = int(match[0]) + frame_end = re.sub(step_pattern, "", frame_end) + + output.extend( + range(int(frame_start), int(frame_end) + 1, step) + ) else: - frames_to_render.append(int(frame)) - frames_to_render.sort() - return frames_to_render + output.append(int(frame)) + output.sort() + return output def _get_real_files_to_render(collection, frames_to_render): @@ -502,22 +532,23 @@ def _get_real_files_to_render(collection, frames_to_render): This range would override and filter previously prepared expected files from DCC. + Example: + >>> expected_files = clique.parse([ + >>> "foo_v01.0001.exr", + >>> "foo_v01.0002.exr", + >>> ]) + >>> frames_to_render = [1] + >>> _get_real_files_to_render(expected_files, frames_to_render) + ["foo_v01.0001.exr"] + Args: collection (clique.Collection): absolute paths frames_to_render (list[int]): of int 1001 + Returns: - (list[str]) + list[str]: absolute paths of files to be rendered - Example: - -------- - expectedFiles = [ - "foo_v01.0001.exr", - "foo_v01.0002.exr", - ] - frames_to_render = 1 - >> - ["foo_v01.0001.exr"] - only explicitly requested frame returned """ included_frames = set(collection.indexes).intersection(frames_to_render) real_collection = clique.Collection( @@ -526,13 +557,17 @@ def _get_real_files_to_render(collection, frames_to_render): collection.padding, indexes=included_frames ) - real_full_paths = list(real_collection) - return [os.path.basename(file_url) for file_url in real_full_paths] + return list(real_collection) -def create_instances_for_aov(instance, skeleton, aov_filter, - skip_integration_repre_list, - do_not_add_review): +def create_instances_for_aov( + instance, + skeleton, + aov_filter, + skip_integration_repre_list, + do_not_add_review, + frames_to_render=None +): """Create instances from AOVs. This will create new pyblish.api.Instances by going over expected @@ -544,6 +579,7 @@ def create_instances_for_aov(instance, skeleton, aov_filter, aov_filter (dict): AOV filter. skip_integration_repre_list (list): skip do_not_add_review (bool): Explicitly disable reviews + frames_to_render (str | None): Frames to render. Returns: list of pyblish.api.Instance: Instances created from @@ -590,7 +626,8 @@ def create_instances_for_aov(instance, skeleton, aov_filter, aov_filter, additional_color_data, skip_integration_repre_list, - do_not_add_review + do_not_add_review, + frames_to_render ) @@ -719,8 +756,15 @@ def get_product_name_and_group_from_template( return resulting_product_name, resulting_group_name -def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, - skip_integration_repre_list, do_not_add_review): +def _create_instances_for_aov( + instance, + skeleton, + aov_filter, + additional_data, + skip_integration_repre_list, + do_not_add_review, + frames_to_render +): """Create instance for each AOV found. This will create new instance for every AOV it can detect in expected @@ -734,7 +778,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, skip_integration_repre_list (list): list of extensions that shouldn't be published do_not_add_review (bool): explicitly disable review - + frames_to_render (str | None): implicit or explicit range of + frames to render this value is sent to Deadline in JobInfo.Frames Returns: list of instances @@ -754,10 +799,23 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # go through AOVs in expected files for aov, files in expected_files[0].items(): collected_files = _collect_expected_files_for_aov(files) + first_filepath = collected_files + if isinstance(first_filepath, (list, tuple)): + first_filepath = first_filepath[0] + staging_dir = os.path.dirname(first_filepath) - expected_filepath = collected_files - if isinstance(collected_files, (list, tuple)): - expected_filepath = collected_files[0] + if ( + frames_to_render is not None + and isinstance(collected_files, (list, tuple)) # not single file + ): + aov_frames_to_render = convert_frames_str_to_list(frames_to_render) + collections, _ = clique.assemble(collected_files) + collected_files = _get_real_files_to_render( + collections[0], aov_frames_to_render) + else: + frame_start = int(skeleton.get("frameStartHandle")) + frame_end = int(skeleton.get("frameEndHandle")) + aov_frames_to_render = list(range(frame_start, frame_end + 1)) dynamic_data = { "aov": aov, @@ -768,7 +826,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # TODO: this must be changed to be more robust. Any coincidence # of camera name in the file path will be considered as # camera name. This is not correct. - camera = [cam for cam in cameras if cam in expected_filepath] + camera = [cam for cam in cameras if cam in first_filepath] # Is there just one camera matching? # TODO: this is not true, we can have multiple cameras in the scene @@ -813,10 +871,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, dynamic_data=dynamic_data ) - staging = os.path.dirname(expected_filepath) - try: - staging = remap_source(staging, anatomy) + staging_dir = remap_source(staging_dir, anatomy) except ValueError as e: log.warning(e) @@ -824,7 +880,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, app = os.environ.get("AYON_HOST_NAME", "") - render_file_name = os.path.basename(expected_filepath) + render_file_name = os.path.basename(first_filepath) aov_patterns = aov_filter @@ -881,10 +937,10 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, "name": ext, "ext": ext, "files": collected_files, - "frameStart": int(skeleton["frameStartHandle"]), - "frameEnd": int(skeleton["frameEndHandle"]), + "frameStart": aov_frames_to_render[0], + "frameEnd": aov_frames_to_render[-1], # If expectedFile are absolute, we need only filenames - "stagingDir": staging, + "stagingDir": staging_dir, "fps": new_instance.get("fps"), "tags": ["review"] if preview else [], "colorspaceData": { diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 1fb906fd65..b601914acd 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -13,15 +13,7 @@ from .utils import get_representation_path_from_context class LoaderPlugin(list): - """Load representation into host application - - Arguments: - context (dict): avalon-core:context-1.0 - - .. versionadded:: 4.0 - This class was introduced - - """ + """Load representation into host application""" product_types = set() representations = set() diff --git a/client/ayon_core/pipeline/project_folders.py b/client/ayon_core/pipeline/project_folders.py index 902b969457..def2af9ba1 100644 --- a/client/ayon_core/pipeline/project_folders.py +++ b/client/ayon_core/pipeline/project_folders.py @@ -1,6 +1,8 @@ +from __future__ import annotations import os import re import json +from typing import Any, Union from ayon_core.settings import get_project_settings from ayon_core.lib import Logger @@ -9,7 +11,7 @@ from .anatomy import Anatomy from .template_data import get_project_template_data -def concatenate_splitted_paths(split_paths, anatomy): +def concatenate_splitted_paths(split_paths, anatomy: Anatomy): log = Logger.get_logger("concatenate_splitted_paths") pattern_array = re.compile(r"\[.*\]") output = [] @@ -47,7 +49,7 @@ def concatenate_splitted_paths(split_paths, anatomy): return output -def fill_paths(path_list, anatomy): +def fill_paths(path_list: list[str], anatomy: Anatomy): format_data = get_project_template_data(project_name=anatomy.project_name) format_data["root"] = anatomy.roots filled_paths = [] @@ -59,7 +61,7 @@ def fill_paths(path_list, anatomy): return filled_paths -def create_project_folders(project_name, basic_paths=None): +def create_project_folders(project_name: str, basic_paths=None): log = Logger.get_logger("create_project_folders") anatomy = Anatomy(project_name) if basic_paths is None: @@ -80,8 +82,19 @@ def create_project_folders(project_name, basic_paths=None): os.makedirs(path) -def _list_path_items(folder_structure): +def _list_path_items( + folder_structure: Union[dict[str, Any], list[str]]): output = [] + + # Allow leaf folders of the `project_folder_structure` to use a list of + # strings instead of a dictionary of keys with empty values. + if isinstance(folder_structure, list): + if not all(isinstance(item, str) for item in folder_structure): + raise ValueError( + f"List items must all be strings. Got: {folder_structure}") + return [[path] for path in folder_structure] + + # Process key, value as key for folder names and value its subfolders for key, value in folder_structure.items(): if not value: output.append(key) @@ -99,7 +112,7 @@ def _list_path_items(folder_structure): return output -def get_project_basic_paths(project_name): +def get_project_basic_paths(project_name: str): project_settings = get_project_settings(project_name) folder_structure = ( project_settings["core"]["project_folder_structure"] diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index cc5f67c74b..49ecab2221 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -1,3 +1,5 @@ +"""Library functions for publishing.""" +from __future__ import annotations import os import sys import inspect @@ -12,8 +14,8 @@ import pyblish.plugin import pyblish.api from ayon_core.lib import ( - Logger, import_filepath, + Logger, filter_profiles, ) from ayon_core.settings import get_project_settings @@ -163,7 +165,7 @@ class HelpContent: def load_help_content_from_filepath(filepath): """Load help content from xml file. - Xml file may containt errors and warnings. + Xml file may contain errors and warnings. """ errors = {} warnings = {} @@ -208,8 +210,9 @@ def load_help_content_from_plugin(plugin): return load_help_content_from_filepath(filepath) -def publish_plugins_discover(paths=None): - """Find and return available pyblish plug-ins +def publish_plugins_discover( + paths: Optional[list[str]] = None) -> DiscoverResult: + """Find and return available pyblish plug-ins. Overridden function from `pyblish` module to be able to collect crashed files and reason of their crash. @@ -252,17 +255,14 @@ def publish_plugins_discover(paths=None): continue try: - module = import_filepath(abspath, mod_name) + module = import_filepath( + abspath, mod_name, sys_module_name=mod_name) - # Store reference to original module, to avoid - # garbage collection from collecting it's global - # imports, such as `import os`. - sys.modules[abspath] = module - - except Exception as err: + except Exception as err: # noqa: BLE001 + # we need broad exception to catch all possible errors. result.crashed_file_paths[abspath] = sys.exc_info() - log.debug("Skipped: \"%s\" (%s)", mod_name, err) + log.debug('Skipped: "%s" (%s)', mod_name, err) continue for plugin in pyblish.plugin.plugins_from_module(module): @@ -280,9 +280,8 @@ def publish_plugins_discover(paths=None): continue plugin_names.append(plugin.__name__) - - plugin.__module__ = module.__file__ - key = "{0}.{1}".format(plugin.__module__, plugin.__name__) + plugin.__file__ = module.__file__ + key = f"{module.__file__}.{plugin.__name__}" plugins[key] = plugin # Include plug-ins from registration. @@ -361,7 +360,7 @@ def get_plugin_settings(plugin, project_settings, log, category=None): # Settings category determined from path # - usually path is './/plugins/publish/' # - category can be host name of addon name ('maya', 'deadline', ...) - filepath = os.path.normpath(inspect.getsourcefile(plugin)) + filepath = os.path.normpath(inspect.getfile(plugin)) split_path = filepath.rsplit(os.path.sep, 5) if len(split_path) < 4: @@ -427,7 +426,7 @@ def filter_pyblish_plugins(plugins): log = Logger.get_logger("filter_pyblish_plugins") # TODO: Don't use host from 'pyblish.api' but from defined host by us. - # - kept becau on farm is probably used host 'shell' which propably + # - kept because on farm is probably used host 'shell' which probably # affect how settings are applied there host_name = pyblish.api.current_host() project_name = os.environ.get("AYON_PROJECT_NAME") @@ -529,7 +528,7 @@ def filter_instances_for_context_plugin(plugin, context): Args: plugin (pyblish.api.Plugin): Plugin with filters. - context (pyblish.api.Context): Pyblish context with insances. + context (pyblish.api.Context): Pyblish context with instances. Returns: Iterator[pyblish.lib.Instance]: Iteration of valid instances. diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 3c11a016ec..1f2c2a89af 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -280,6 +280,9 @@ class ExtractOIIOTranscode(publish.Extractor): collection = collections[0] frames = list(collection.indexes) + if collection.holes(): + return files_to_convert + frame_str = "{}-{}#".format(frames[0], frames[-1]) file_name = "{}{}{}".format(collection.head, frame_str, collection.tail) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 7c38b0453b..a3db16c898 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -196,7 +196,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ).format(repre_name)) continue - input_ext = repre["ext"] + input_ext = repre["ext"].lower() if input_ext.startswith("."): input_ext = input_ext[1:] diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index e8fe09bab7..ae043a10a9 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -706,7 +706,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # In case source are published in place we need to # skip renumbering repre_frame_start = repre.get("frameStart") - if repre_frame_start is not None: + explicit_frames = instance.data.get("hasExplicitFrames", False) + if not explicit_frames and repre_frame_start is not None: index_frame_start = int(repre_frame_start) # Shift destination sequence to the start frame destination_indexes = [ diff --git a/client/ayon_core/plugins/publish/integrate_attach_reviewable.py b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py new file mode 100644 index 0000000000..b98d8d28fe --- /dev/null +++ b/client/ayon_core/plugins/publish/integrate_attach_reviewable.py @@ -0,0 +1,138 @@ +import copy +import pyblish.api +from typing import List + +from ayon_core.lib import EnumDef +from ayon_core.pipeline import OptionalPyblishPluginMixin + + +class AttachReviewables( + pyblish.api.InstancePlugin, OptionalPyblishPluginMixin +): + """Attach reviewable to other instances + + This pre-integrator plugin allows instances to be 'attached to' other + instances by moving all its representations over to the other instance. + Even though this technically could work for any representation the current + intent is to use for reviewables only, like e.g. `review` or `render` + product type. + + When the reviewable is attached to another instance, the instance itself + will not be published as a separate entity. Instead, the representations + will be copied/moved to the instances it is attached to. + """ + + families = ["render", "review"] + order = pyblish.api.IntegratorOrder - 0.499 + label = "Attach reviewables" + + settings_category = "core" + + def process(self, instance): + # TODO: Support farm. + # If instance is being submitted to the farm we should pass through + # the 'attached reviewables' metadata to the farm job + # TODO: Reviewable frame range and resolutions + # Because we are attaching the data to another instance, how do we + # correctly propagate the resolution + frame rate to the other + # instance? Do we even need to? + # TODO: If this were to attach 'renders' to another instance that would + # mean there wouldn't necessarily be a render publish separate as a + # result. Is that correct expected behavior? + attr_values = self.get_attr_values_from_data(instance.data) + attach_to = attr_values.get("attach", []) + if not attach_to: + self.log.debug( + "Reviewable is not set to attach to another instance." + ) + return + + attach_instances: List[pyblish.api.Instance] = [] + for attach_instance_id in attach_to: + # Find the `pyblish.api.Instance` matching the `CreatedInstance.id` + # in the `attach_to` list + attach_instance = next( + ( + _inst + for _inst in instance.context + if _inst.data.get("instance_id") == attach_instance_id + ), + None, + ) + if attach_instance is None: + continue + + # Skip inactive instances + if not attach_instance.data.get("active", True): + continue + + # For now do not support attaching to 'farm' instances until we + # can pass the 'attaching' on to the farm jobs. + if attach_instance.data.get("farm"): + self.log.warning( + "Attaching to farm instances is not supported yet." + ) + continue + + attach_instances.append(attach_instance) + + instances_names = ", ".join( + instance.name for instance in attach_instances + ) + self.log.info( + f"Attaching reviewable to other instances: {instances_names}" + ) + + # Copy the representations of this reviewable instance to the other + # instance + representations = instance.data.get("representations", []) + for attach_instance in attach_instances: + self.log.info(f"Attaching to {attach_instance.name}") + attach_instance.data.setdefault("representations", []).extend( + copy.deepcopy(representations) + ) + + # Delete representations on the reviewable instance itself + for repre in representations: + self.log.debug( + "Marking representation as deleted because it was " + f"attached to other instances instead: {repre}" + ) + repre.setdefault("tags", []).append("delete") + + # Stop integrator from trying to integrate this instance + if attach_to: + instance.data["integrate"] = False + + @classmethod + def get_attr_defs_for_instance(cls, create_context, instance): + # TODO: Check if instance is actually a 'reviewable' + # Filtering of instance, if needed, can be customized + if not cls.instance_matches_plugin_families(instance): + return [] + + items = [] + for other_instance in create_context.instances: + if other_instance == instance: + continue + + # Do not allow attaching to other reviewable instances + if other_instance.data["productType"] in cls.families: + continue + + items.append( + { + "label": other_instance.label, + "value": str(other_instance.id), + } + ) + + return [ + EnumDef( + "attach", + label="Attach reviewable", + multiselection=True, + items=items, + tooltip="Attach this reviewable to another instance", + ) + ] diff --git a/client/ayon_core/tools/pyblish_pype/__init__.py b/client/ayon_core/tools/pyblish_pype/__init__.py deleted file mode 100644 index ef507005a5..0000000000 --- a/client/ayon_core/tools/pyblish_pype/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .version import version, version_info, __version__ - -# This must be run prior to importing the application, due to the -# application requiring a discovered copy of Qt bindings. - -from .app import show - -__all__ = [ - 'show', - 'version', - 'version_info', - '__version__' -] diff --git a/client/ayon_core/tools/pyblish_pype/__main__.py b/client/ayon_core/tools/pyblish_pype/__main__.py deleted file mode 100644 index 5fc1b44a35..0000000000 --- a/client/ayon_core/tools/pyblish_pype/__main__.py +++ /dev/null @@ -1,19 +0,0 @@ -from .app import show - - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument("--debug", action="store_true") - - args = parser.parse_args() - - if args.debug: - from . import mock - import pyblish.api - - for Plugin in mock.plugins: - pyblish.api.register_plugin(Plugin) - - show() diff --git a/client/ayon_core/tools/pyblish_pype/app.css b/client/ayon_core/tools/pyblish_pype/app.css deleted file mode 100644 index 33b6acbddb..0000000000 --- a/client/ayon_core/tools/pyblish_pype/app.css +++ /dev/null @@ -1,539 +0,0 @@ -/* Global CSS */ - -* { - outline: none; - color: #ddd; - font-family: "Open Sans"; - font-style: normal; -} - -/* General CSS */ - -QWidget { - background: #555; - background-position: center center; - background-repeat: no-repeat; - font-size: 12px; -} - -QMenu { - background-color: #555; /* sets background of the menu */ - border: 1px solid #222; -} - -QMenu::item { - /* sets background of menu item. set this to something non-transparent - if you want menu color and menu item color to be different */ - background-color: transparent; - padding: 5px; - padding-left: 30px; -} - -QMenu::item:selected { /* when user selects item using mouse or keyboard */ - background-color: #666; -} - -QDialog { - min-width: 300; - background: "#555"; -} - -QListView { - border: 0px; - background: "transparent" -} - -QTreeView { - border: 0px; - background: "transparent" -} - -QPushButton { - width: 27px; - height: 27px; - background: #555; - border: 1px solid #aaa; - border-radius: 4px; - font-family: "FontAwesome"; - font-size: 11pt; - color: white; - padding: 0px; -} - -QPushButton:pressed { - background: "#777"; -} - -QPushButton:hover { - color: white; - background: "#666"; -} - -QPushButton:disabled { - color: rgba(255, 255, 255, 50); -} - -QTextEdit, QLineEdit { - background: #555; - border: 1px solid #333; - font-size: 9pt; - color: #fff; -} - -QCheckBox { - min-width: 17px; - max-width: 17px; - border: 1px solid #222; - background: transparent; -} - -QCheckBox::indicator { - width: 15px; - height: 15px; - /*background: #444;*/ - background: transparent; - border: 1px solid #555; -} - -QCheckBox::indicator:checked { - background: #222; -} - -QComboBox { - background: #444; - color: #EEE; - font-size: 8pt; - border: 1px solid #333; - padding: 0px; -} - -QComboBox[combolist="true"]::drop-down { - background: transparent; -} - -QComboBox[combolist="true"]::down-arrow { - max-width: 0px; - width: 1px; -} - -QComboBox[combolist="true"] QAbstractItemView { - background: #555; -} - -QScrollBar:vertical { - border: none; - background: transparent; - width: 6px; - margin: 0; -} - -QScrollBar::handle:vertical { - background: #333; - border-radius: 3px; - min-height: 20px; -} - -QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; -} - -QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { - border: 1px solid #444; - width: 3px; - height: 3px; - background: white; -} - -QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; -} - -QToolTip { - color: #eee; - background-color: #555; - border: none; - padding: 5px; -} - -QLabel { - border-radius: 0px; -} - -QToolButton { - background-color: transparent; - margin: 0px; - padding: 0px; - border-radius: 0px; - border: none; -} - -/* Specific CSS */ -#PerspectiveToggleBtn { - border-bottom: 3px solid lightblue; - border-top: 0px; - border-radius: 0px; - border-right: 1px solid #232323; - border-left: 0px; - font-size: 26pt; - font-family: "FontAwesome"; -} - -#Terminal QComboBox::drop-down { - width: 60px; -} - -#Header { - background: #555; - border: 1px solid #444; - padding: 0px; - margin: 0px; -} - -#Header QRadioButton { - border: 3px solid "transparent"; - border-right: 1px solid #333; - left: 2px; -} - -#Header QRadioButton::indicator { - width: 65px; - height: 40px; - background-repeat: no-repeat; - background-position: center center; - image: none; -} - -#Header QRadioButton:hover { - background-color: rgba(255, 255, 255, 10); -} - -#Header QRadioButton:checked { - background-color: rgba(255, 255, 255, 20); - border-bottom: 3px solid "lightblue"; -} - -#Body { - padding: 0px; - border: 1px solid #333; - background: #444; -} - -#Body QWidget { - background: #444; -} - -#Header #TerminalTab { - background-image: url("img/tab-terminal.png"); -} - -#Header #OverviewTab { - background-image: url("img/tab-overview.png"); -} - -#ButtonWithMenu { - background: #555; - border: 1px solid #fff; - border-radius: 4px; - font-family: "FontAwesome"; - font-size: 11pt; - color: white; -} - -#ButtonWithMenu:pressed { - background: #777; -} - -#ButtonWithMenu:hover { - color: white; - background: #666; -} -#ButtonWithMenu:disabled { - background: #666; - color: #999; - border: 1px solid #999; -} - -#FooterSpacer, #FooterInfo, #HeaderSpacer { - background: transparent; -} - -#Footer { - background: #555; - min-height: 43px; -} - -#Footer[success="1"] { - background: #458056 -} - -#Footer[success="0"] { - background-color: #AA5050 -} - -#Footer QPushButton { - background: #555; - border: 1px solid #aaa; - border-radius: 4px; - font-family: "FontAwesome"; - font-size: 11pt; - color: white; - padding: 0px; -} - -#Footer QPushButton:pressed:hover { - color: #3784c5; - background: #444; -} - -#Footer QPushButton:hover { - background: #505050; - border: 2px solid #3784c5; -} - -#Footer QPushButton:disabled { - border: 1px solid #888; - background: #666; - color: #999; -} - -#ClosingPlaceholder { - background: rgba(0, 0, 0, 50); -} - -#CommentIntentWidget { - background: transparent; -} - -#CommentBox, #CommentPlaceholder { - font-family: "Open Sans"; - font-size: 8pt; - padding: 5px; - background: #444; -} - -#CommentBox { - selection-background-color: #222; -} - -#CommentBox:disabled, #CommentPlaceholder:disabled, #IntentBox:disabled { - background: #555; -} - -#CommentPlaceholder { - color: #888 -} - -#IntentBox { - background: #444; - font-size: 8pt; - padding: 5px; - min-width: 75px; - color: #EEE; -} - -#IntentBox::drop-down:button { - border: 0px; - background: transparent; -} - -#IntentBox::down-arrow { - image: url("/img/down_arrow.png"); -} - -#IntentBox::down-arrow:disabled { - image: url(); -} - -#TerminalView { - background-color: transparent; -} - -#TerminalView:item { - background-color: transparent; -} - -#TerminalView:hover { - background-color: transparent; -} - -#TerminalView:selected { - background-color: transparent; -} - -#TerminalView:item:hover { - color: #ffffff; -} - -#TerminalView:item:selected { - color: #eeeeee; -} - -#TerminalView QTextEdit { - padding:3px; - color: #aaa; - border-radius: 7px; - border-color: #222; - border-style: solid; - border-width: 2px; - background-color: #333; -} - -#TerminalView QTextEdit:hover { - background-color: #353535; -} - -#TerminalView QTextEdit:selected { - background-color: #303030; -} - -#ExpandableWidgetContent { - border: none; - background-color: #232323; - color:#eeeeee; -} - -#EllidableLabel { - font-size: 16pt; - font-weight: normal; -} - -#PerspectiveScrollContent { - border: 1px solid #333; - border-radius: 0px; -} - -#PerspectiveWidgetContent{ - padding: 0px; -} - -#PerspectiveLabel { - background-color: transparent; - border: none; -} - -#PerspectiveIndicator { - font-size: 16pt; - font-weight: normal; - padding: 5px; - background-color: #ffffff; - color: #333333; -} - -#PerspectiveIndicator[state="warning"] { - background-color: #ff9900; - color: #ffffff; -} - -#PerspectiveIndicator[state="active"] { - background-color: #99CEEE; - color: #ffffff; -} - -#PerspectiveIndicator[state="error"] { - background-color: #cc4a4a; - color: #ffffff; -} - -#PerspectiveIndicator[state="ok"] { - background-color: #69a567; - color: #ffffff; -} - -#ExpandableHeader { - background-color: transparent; - margin: 0px; - padding: 0px; - border-radius: 0px; - border: none; -} - -#ExpandableHeader QWidget { - color: #ddd; -} - -#ExpandableHeader QWidget:hover { - color: #fff; -} - -#TerminalFilterWidget QPushButton { - /* font: %(font_size_pt)spt; */ - font-family: "FontAwesome"; - text-align: center; - background-color: transparent; - border-width: 1px; - border-color: #777777; - border-style: none; - padding: 0px; - border-radius: 8px; -} -#TerminalFilterWidget QPushButton:hover { - background: #5f5f5f; - border-style: none; -} -#TerminalFilterWidget QPushButton:pressed { - background: #606060; - border-style: none; -} -#TerminalFilterWidget QPushButton:pressed:hover { - background: #626262; - border-style: none; -} - -#TerminalFilerBtn[type="info"]:checked {color: rgb(255, 255, 255);} -#TerminalFilerBtn[type="info"]:hover:pressed {color: rgba(255, 255, 255, 163);} -#TerminalFilerBtn[type="info"] {color: rgba(255, 255, 255, 63);} - -#TerminalFilerBtn[type="error"]:checked {color: rgb(255, 74, 74);} -#TerminalFilerBtn[type="error"]:hover:pressed {color: rgba(255, 74, 74, 163);} -#TerminalFilerBtn[type="error"] {color: rgba(255, 74, 74, 63);} - -#TerminalFilerBtn[type="log_debug"]:checked {color: rgb(255, 102, 232);} -#TerminalFilerBtn[type="log_debug"] {color: rgba(255, 102, 232, 63);} -#TerminalFilerBtn[type="log_debug"]:hover:pressed { - color: rgba(255, 102, 232, 163); -} - -#TerminalFilerBtn[type="log_info"]:checked {color: rgb(102, 171, 255);} -#TerminalFilerBtn[type="log_info"] {color: rgba(102, 171, 255, 63);} -#TerminalFilerBtn[type="log_info"]:hover:pressed { - color: rgba(102, 171, 255, 163); -} - -#TerminalFilerBtn[type="log_warning"]:checked {color: rgb(255, 186, 102);} -#TerminalFilerBtn[type="log_warning"] {color: rgba(255, 186, 102, 63);} -#TerminalFilerBtn[type="log_warning"]:hover:pressed { - color: rgba(255, 186, 102, 163); -} - -#TerminalFilerBtn[type="log_error"]:checked {color: rgb(255, 77, 88);} -#TerminalFilerBtn[type="log_error"] {color: rgba(255, 77, 88, 63);} -#TerminalFilerBtn[type="log_error"]:hover:pressed { - color: rgba(255, 77, 88, 163); -} - -#TerminalFilerBtn[type="log_critical"]:checked {color: rgb(255, 79, 117);} -#TerminalFilerBtn[type="log_critical"] {color: rgba(255, 79, 117, 63);} -#TerminalFilerBtn[type="log_critical"]:hover:pressed { - color: rgba(255, 79, 117, 163); -} - -#SuspendLogsBtn { - background: #444; - border: none; - border-top-right-radius: 7px; - border-bottom-right-radius: 7px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - font-family: "FontAwesome"; - font-size: 11pt; - color: white; - padding: 0px; -} - -#SuspendLogsBtn:hover { - background: #333; -} - -#SuspendLogsBtn:disabled { - background: #4c4c4c; -} diff --git a/client/ayon_core/tools/pyblish_pype/app.py b/client/ayon_core/tools/pyblish_pype/app.py deleted file mode 100644 index bdc204bfbd..0000000000 --- a/client/ayon_core/tools/pyblish_pype/app.py +++ /dev/null @@ -1,110 +0,0 @@ -from __future__ import print_function - -import os -import sys -import ctypes -import platform -import contextlib - -from qtpy import QtCore, QtGui, QtWidgets - -from . import control, settings, util, window - -self = sys.modules[__name__] - -# Maintain reference to currently opened window -self._window = None - - -@contextlib.contextmanager -def application(): - app = QtWidgets.QApplication.instance() - - if not app: - print("Starting new QApplication..") - app = QtWidgets.QApplication(sys.argv) - yield app - app.exec_() - else: - print("Using existing QApplication..") - yield app - if os.environ.get("PYBLISH_GUI_ALWAYS_EXEC"): - app.exec_() - - -def install_translator(app): - translator = QtCore.QTranslator(app) - translator.load(QtCore.QLocale.system(), "i18n/", - directory=util.root) - app.installTranslator(translator) - print("Installed translator") - - -def install_fonts(): - database = QtGui.QFontDatabase() - fonts = [ - "opensans/OpenSans-Bold.ttf", - "opensans/OpenSans-BoldItalic.ttf", - "opensans/OpenSans-ExtraBold.ttf", - "opensans/OpenSans-ExtraBoldItalic.ttf", - "opensans/OpenSans-Italic.ttf", - "opensans/OpenSans-Light.ttf", - "opensans/OpenSans-LightItalic.ttf", - "opensans/OpenSans-Regular.ttf", - "opensans/OpenSans-Semibold.ttf", - "opensans/OpenSans-SemiboldItalic.ttf", - "fontawesome/fontawesome-webfont.ttf" - ] - - for font in fonts: - path = util.get_asset("font", font) - - # TODO(marcus): Check if they are already installed first. - # In hosts, this will be called each time the GUI is shown, - # potentially installing a font each time. - if database.addApplicationFont(path) < 0: - print("Could not install %s" % path) - else: - print("Installed %s" % font) - - -def on_destroyed(): - """Remove internal reference to window on window destroyed""" - self._window = None - - -def show(parent=None): - with open(util.get_asset("app.css")) as f: - css = f.read() - - # Make relative paths absolute - root = util.get_asset("").replace("\\", "/") - css = css.replace("url(\"", "url(\"%s" % root) - - with application() as app: - - if platform.system().lower() == "windows": - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( - u"pyblish_pype" - ) - - install_fonts() - install_translator(app) - - if self._window is None: - ctrl = control.Controller() - self._window = window.Window(ctrl, parent) - self._window.destroyed.connect(on_destroyed) - - self._window.show() - self._window.activateWindow() - self._window.setWindowTitle(settings.WindowTitle) - - font = QtGui.QFont("Open Sans", 8, QtGui.QFont.Normal) - self._window.setFont(font) - self._window.setStyleSheet(css) - - self._window.reset() - self._window.resize(*settings.WindowSize) - - return self._window diff --git a/client/ayon_core/tools/pyblish_pype/awesome.py b/client/ayon_core/tools/pyblish_pype/awesome.py deleted file mode 100644 index c70f5b1064..0000000000 --- a/client/ayon_core/tools/pyblish_pype/awesome.py +++ /dev/null @@ -1,733 +0,0 @@ - -tags = { - "500px": u"\uf26e", - "adjust": u"\uf042", - "adn": u"\uf170", - "align-center": u"\uf037", - "align-justify": u"\uf039", - "align-left": u"\uf036", - "align-right": u"\uf038", - "amazon": u"\uf270", - "ambulance": u"\uf0f9", - "american-sign-language-interpreting": u"\uf2a3", - "anchor": u"\uf13d", - "android": u"\uf17b", - "angellist": u"\uf209", - "angle-double-down": u"\uf103", - "angle-double-left": u"\uf100", - "angle-double-right": u"\uf101", - "angle-double-up": u"\uf102", - "angle-down": u"\uf107", - "angle-left": u"\uf104", - "angle-right": u"\uf105", - "angle-up": u"\uf106", - "apple": u"\uf179", - "archive": u"\uf187", - "area-chart": u"\uf1fe", - "arrow-circle-down": u"\uf0ab", - "arrow-circle-left": u"\uf0a8", - "arrow-circle-o-down": u"\uf01a", - "arrow-circle-o-left": u"\uf190", - "arrow-circle-o-right": u"\uf18e", - "arrow-circle-o-up": u"\uf01b", - "arrow-circle-right": u"\uf0a9", - "arrow-circle-up": u"\uf0aa", - "arrow-down": u"\uf063", - "arrow-left": u"\uf060", - "arrow-right": u"\uf061", - "arrow-up": u"\uf062", - "arrows": u"\uf047", - "arrows-alt": u"\uf0b2", - "arrows-h": u"\uf07e", - "arrows-v": u"\uf07d", - "asl-interpreting (alias)": u"\uf2a3", - "assistive-listening-systems": u"\uf2a2", - "asterisk": u"\uf069", - "at": u"\uf1fa", - "audio-description": u"\uf29e", - "automobile (alias)": u"\uf1b9", - "backward": u"\uf04a", - "balance-scale": u"\uf24e", - "ban": u"\uf05e", - "bank (alias)": u"\uf19c", - "bar-chart": u"\uf080", - "bar-chart-o (alias)": u"\uf080", - "barcode": u"\uf02a", - "bars": u"\uf0c9", - "battery-0 (alias)": u"\uf244", - "battery-1 (alias)": u"\uf243", - "battery-2 (alias)": u"\uf242", - "battery-3 (alias)": u"\uf241", - "battery-4 (alias)": u"\uf240", - "battery-empty": u"\uf244", - "battery-full": u"\uf240", - "battery-half": u"\uf242", - "battery-quarter": u"\uf243", - "battery-three-quarters": u"\uf241", - "bed": u"\uf236", - "beer": u"\uf0fc", - "behance": u"\uf1b4", - "behance-square": u"\uf1b5", - "bell": u"\uf0f3", - "bell-o": u"\uf0a2", - "bell-slash": u"\uf1f6", - "bell-slash-o": u"\uf1f7", - "bicycle": u"\uf206", - "binoculars": u"\uf1e5", - "birthday-cake": u"\uf1fd", - "bitbucket": u"\uf171", - "bitbucket-square": u"\uf172", - "bitcoin (alias)": u"\uf15a", - "black-tie": u"\uf27e", - "blind": u"\uf29d", - "bluetooth": u"\uf293", - "bluetooth-b": u"\uf294", - "bold": u"\uf032", - "bolt": u"\uf0e7", - "bomb": u"\uf1e2", - "book": u"\uf02d", - "bookmark": u"\uf02e", - "bookmark-o": u"\uf097", - "braille": u"\uf2a1", - "briefcase": u"\uf0b1", - "btc": u"\uf15a", - "bug": u"\uf188", - "building": u"\uf1ad", - "building-o": u"\uf0f7", - "bullhorn": u"\uf0a1", - "bullseye": u"\uf140", - "bus": u"\uf207", - "buysellads": u"\uf20d", - "cab (alias)": u"\uf1ba", - "calculator": u"\uf1ec", - "calendar": u"\uf073", - "calendar-check-o": u"\uf274", - "calendar-minus-o": u"\uf272", - "calendar-o": u"\uf133", - "calendar-plus-o": u"\uf271", - "calendar-times-o": u"\uf273", - "camera": u"\uf030", - "camera-retro": u"\uf083", - "car": u"\uf1b9", - "caret-down": u"\uf0d7", - "caret-left": u"\uf0d9", - "caret-right": u"\uf0da", - "caret-square-o-down": u"\uf150", - "caret-square-o-left": u"\uf191", - "caret-square-o-right": u"\uf152", - "caret-square-o-up": u"\uf151", - "caret-up": u"\uf0d8", - "cart-arrow-down": u"\uf218", - "cart-plus": u"\uf217", - "cc": u"\uf20a", - "cc-amex": u"\uf1f3", - "cc-diners-club": u"\uf24c", - "cc-discover": u"\uf1f2", - "cc-jcb": u"\uf24b", - "cc-mastercard": u"\uf1f1", - "cc-paypal": u"\uf1f4", - "cc-stripe": u"\uf1f5", - "cc-visa": u"\uf1f0", - "certificate": u"\uf0a3", - "chain (alias)": u"\uf0c1", - "chain-broken": u"\uf127", - "check": u"\uf00c", - "check-circle": u"\uf058", - "check-circle-o": u"\uf05d", - "check-square": u"\uf14a", - "check-square-o": u"\uf046", - "chevron-circle-down": u"\uf13a", - "chevron-circle-left": u"\uf137", - "chevron-circle-right": u"\uf138", - "chevron-circle-up": u"\uf139", - "chevron-down": u"\uf078", - "chevron-left": u"\uf053", - "chevron-right": u"\uf054", - "chevron-up": u"\uf077", - "child": u"\uf1ae", - "chrome": u"\uf268", - "circle": u"\uf111", - "circle-o": u"\uf10c", - "circle-o-notch": u"\uf1ce", - "circle-thin": u"\uf1db", - "clipboard": u"\uf0ea", - "clock-o": u"\uf017", - "clone": u"\uf24d", - "close (alias)": u"\uf00d", - "cloud": u"\uf0c2", - "cloud-download": u"\uf0ed", - "cloud-upload": u"\uf0ee", - "cny (alias)": u"\uf157", - "code": u"\uf121", - "code-fork": u"\uf126", - "codepen": u"\uf1cb", - "codiepie": u"\uf284", - "coffee": u"\uf0f4", - "cog": u"\uf013", - "cogs": u"\uf085", - "columns": u"\uf0db", - "comment": u"\uf075", - "comment-o": u"\uf0e5", - "commenting": u"\uf27a", - "commenting-o": u"\uf27b", - "comments": u"\uf086", - "comments-o": u"\uf0e6", - "compass": u"\uf14e", - "compress": u"\uf066", - "connectdevelop": u"\uf20e", - "contao": u"\uf26d", - "copy (alias)": u"\uf0c5", - "copyright": u"\uf1f9", - "creative-commons": u"\uf25e", - "credit-card": u"\uf09d", - "credit-card-alt": u"\uf283", - "crop": u"\uf125", - "crosshairs": u"\uf05b", - "css3": u"\uf13c", - "cube": u"\uf1b2", - "cubes": u"\uf1b3", - "cut (alias)": u"\uf0c4", - "cutlery": u"\uf0f5", - "dashboard (alias)": u"\uf0e4", - "dashcube": u"\uf210", - "database": u"\uf1c0", - "deaf": u"\uf2a4", - "deafness (alias)": u"\uf2a4", - "dedent (alias)": u"\uf03b", - "delicious": u"\uf1a5", - "desktop": u"\uf108", - "deviantart": u"\uf1bd", - "diamond": u"\uf219", - "digg": u"\uf1a6", - "dollar (alias)": u"\uf155", - "dot-circle-o": u"\uf192", - "download": u"\uf019", - "dribbble": u"\uf17d", - "dropbox": u"\uf16b", - "drupal": u"\uf1a9", - "edge": u"\uf282", - "edit (alias)": u"\uf044", - "eject": u"\uf052", - "ellipsis-h": u"\uf141", - "ellipsis-v": u"\uf142", - "empire": u"\uf1d1", - "envelope": u"\uf0e0", - "envelope-o": u"\uf003", - "envelope-square": u"\uf199", - "envira": u"\uf299", - "eraser": u"\uf12d", - "eur": u"\uf153", - "euro (alias)": u"\uf153", - "exchange": u"\uf0ec", - "exclamation": u"\uf12a", - "exclamation-circle": u"\uf06a", - "exclamation-triangle": u"\uf071", - "expand": u"\uf065", - "expeditedssl": u"\uf23e", - "external-link": u"\uf08e", - "external-link-square": u"\uf14c", - "eye": u"\uf06e", - "eye-slash": u"\uf070", - "eyedropper": u"\uf1fb", - "fa (alias)": u"\uf2b4", - "facebook": u"\uf09a", - "facebook-f (alias)": u"\uf09a", - "facebook-official": u"\uf230", - "facebook-square": u"\uf082", - "fast-backward": u"\uf049", - "fast-forward": u"\uf050", - "fax": u"\uf1ac", - "feed (alias)": u"\uf09e", - "female": u"\uf182", - "fighter-jet": u"\uf0fb", - "file": u"\uf15b", - "file-archive-o": u"\uf1c6", - "file-audio-o": u"\uf1c7", - "file-code-o": u"\uf1c9", - "file-excel-o": u"\uf1c3", - "file-image-o": u"\uf1c5", - "file-movie-o (alias)": u"\uf1c8", - "file-o": u"\uf016", - "file-pdf-o": u"\uf1c1", - "file-photo-o (alias)": u"\uf1c5", - "file-picture-o (alias)": u"\uf1c5", - "file-powerpoint-o": u"\uf1c4", - "file-sound-o (alias)": u"\uf1c7", - "file-text": u"\uf15c", - "file-text-o": u"\uf0f6", - "file-video-o": u"\uf1c8", - "file-word-o": u"\uf1c2", - "file-zip-o (alias)": u"\uf1c6", - "files-o": u"\uf0c5", - "film": u"\uf008", - "filter": u"\uf0b0", - "fire": u"\uf06d", - "fire-extinguisher": u"\uf134", - "firefox": u"\uf269", - "first-order": u"\uf2b0", - "flag": u"\uf024", - "flag-checkered": u"\uf11e", - "flag-o": u"\uf11d", - "flash (alias)": u"\uf0e7", - "flask": u"\uf0c3", - "flickr": u"\uf16e", - "floppy-o": u"\uf0c7", - "folder": u"\uf07b", - "folder-o": u"\uf114", - "folder-open": u"\uf07c", - "folder-open-o": u"\uf115", - "font": u"\uf031", - "font-awesome": u"\uf2b4", - "fonticons": u"\uf280", - "fort-awesome": u"\uf286", - "forumbee": u"\uf211", - "forward": u"\uf04e", - "foursquare": u"\uf180", - "frown-o": u"\uf119", - "futbol-o": u"\uf1e3", - "gamepad": u"\uf11b", - "gavel": u"\uf0e3", - "gbp": u"\uf154", - "ge (alias)": u"\uf1d1", - "gear (alias)": u"\uf013", - "gears (alias)": u"\uf085", - "genderless": u"\uf22d", - "get-pocket": u"\uf265", - "gg": u"\uf260", - "gg-circle": u"\uf261", - "gift": u"\uf06b", - "git": u"\uf1d3", - "git-square": u"\uf1d2", - "github": u"\uf09b", - "github-alt": u"\uf113", - "github-square": u"\uf092", - "gitlab": u"\uf296", - "gittip (alias)": u"\uf184", - "glass": u"\uf000", - "glide": u"\uf2a5", - "glide-g": u"\uf2a6", - "globe": u"\uf0ac", - "google": u"\uf1a0", - "google-plus": u"\uf0d5", - "google-plus-circle (alias)": u"\uf2b3", - "google-plus-official": u"\uf2b3", - "google-plus-square": u"\uf0d4", - "google-wallet": u"\uf1ee", - "graduation-cap": u"\uf19d", - "gratipay": u"\uf184", - "group (alias)": u"\uf0c0", - "h-square": u"\uf0fd", - "hacker-news": u"\uf1d4", - "hand-grab-o (alias)": u"\uf255", - "hand-lizard-o": u"\uf258", - "hand-o-down": u"\uf0a7", - "hand-o-left": u"\uf0a5", - "hand-o-right": u"\uf0a4", - "hand-o-up": u"\uf0a6", - "hand-paper-o": u"\uf256", - "hand-peace-o": u"\uf25b", - "hand-pointer-o": u"\uf25a", - "hand-rock-o": u"\uf255", - "hand-scissors-o": u"\uf257", - "hand-spock-o": u"\uf259", - "hand-stop-o (alias)": u"\uf256", - "hard-of-hearing (alias)": u"\uf2a4", - "hashtag": u"\uf292", - "hdd-o": u"\uf0a0", - "header": u"\uf1dc", - "headphones": u"\uf025", - "heart": u"\uf004", - "heart-o": u"\uf08a", - "heartbeat": u"\uf21e", - "history": u"\uf1da", - "home": u"\uf015", - "hospital-o": u"\uf0f8", - "hotel (alias)": u"\uf236", - "hourglass": u"\uf254", - "hourglass-1 (alias)": u"\uf251", - "hourglass-2 (alias)": u"\uf252", - "hourglass-3 (alias)": u"\uf253", - "hourglass-end": u"\uf253", - "hourglass-half": u"\uf252", - "hourglass-o": u"\uf250", - "hourglass-start": u"\uf251", - "houzz": u"\uf27c", - "html5": u"\uf13b", - "i-cursor": u"\uf246", - "ils": u"\uf20b", - "image (alias)": u"\uf03e", - "inbox": u"\uf01c", - "indent": u"\uf03c", - "industry": u"\uf275", - "info": u"\uf129", - "info-circle": u"\uf05a", - "inr": u"\uf156", - "instagram": u"\uf16d", - "institution (alias)": u"\uf19c", - "internet-explorer": u"\uf26b", - "intersex (alias)": u"\uf224", - "ioxhost": u"\uf208", - "italic": u"\uf033", - "joomla": u"\uf1aa", - "jpy": u"\uf157", - "jsfiddle": u"\uf1cc", - "key": u"\uf084", - "keyboard-o": u"\uf11c", - "krw": u"\uf159", - "language": u"\uf1ab", - "laptop": u"\uf109", - "lastfm": u"\uf202", - "lastfm-square": u"\uf203", - "leaf": u"\uf06c", - "leanpub": u"\uf212", - "legal (alias)": u"\uf0e3", - "lemon-o": u"\uf094", - "level-down": u"\uf149", - "level-up": u"\uf148", - "life-bouy (alias)": u"\uf1cd", - "life-buoy (alias)": u"\uf1cd", - "life-ring": u"\uf1cd", - "life-saver (alias)": u"\uf1cd", - "lightbulb-o": u"\uf0eb", - "line-chart": u"\uf201", - "link": u"\uf0c1", - "linkedin": u"\uf0e1", - "linkedin-square": u"\uf08c", - "linux": u"\uf17c", - "list": u"\uf03a", - "list-alt": u"\uf022", - "list-ol": u"\uf0cb", - "list-ul": u"\uf0ca", - "location-arrow": u"\uf124", - "lock": u"\uf023", - "long-arrow-down": u"\uf175", - "long-arrow-left": u"\uf177", - "long-arrow-right": u"\uf178", - "long-arrow-up": u"\uf176", - "low-vision": u"\uf2a8", - "magic": u"\uf0d0", - "magnet": u"\uf076", - "mail-forward (alias)": u"\uf064", - "mail-reply (alias)": u"\uf112", - "mail-reply-all (alias)": u"\uf122", - "male": u"\uf183", - "map": u"\uf279", - "map-marker": u"\uf041", - "map-o": u"\uf278", - "map-pin": u"\uf276", - "map-signs": u"\uf277", - "mars": u"\uf222", - "mars-double": u"\uf227", - "mars-stroke": u"\uf229", - "mars-stroke-h": u"\uf22b", - "mars-stroke-v": u"\uf22a", - "maxcdn": u"\uf136", - "meanpath": u"\uf20c", - "medium": u"\uf23a", - "medkit": u"\uf0fa", - "meh-o": u"\uf11a", - "mercury": u"\uf223", - "microphone": u"\uf130", - "microphone-slash": u"\uf131", - "minus": u"\uf068", - "minus-circle": u"\uf056", - "minus-square": u"\uf146", - "minus-square-o": u"\uf147", - "mixcloud": u"\uf289", - "mobile": u"\uf10b", - "mobile-phone (alias)": u"\uf10b", - "modx": u"\uf285", - "money": u"\uf0d6", - "moon-o": u"\uf186", - "mortar-board (alias)": u"\uf19d", - "motorcycle": u"\uf21c", - "mouse-pointer": u"\uf245", - "music": u"\uf001", - "navicon (alias)": u"\uf0c9", - "neuter": u"\uf22c", - "newspaper-o": u"\uf1ea", - "object-group": u"\uf247", - "object-ungroup": u"\uf248", - "odnoklassniki": u"\uf263", - "odnoklassniki-square": u"\uf264", - "opencart": u"\uf23d", - "openid": u"\uf19b", - "opera": u"\uf26a", - "optin-monster": u"\uf23c", - "outdent": u"\uf03b", - "pagelines": u"\uf18c", - "paint-brush": u"\uf1fc", - "paper-plane": u"\uf1d8", - "paper-plane-o": u"\uf1d9", - "paperclip": u"\uf0c6", - "paragraph": u"\uf1dd", - "paste (alias)": u"\uf0ea", - "pause": u"\uf04c", - "pause-circle": u"\uf28b", - "pause-circle-o": u"\uf28c", - "paw": u"\uf1b0", - "paypal": u"\uf1ed", - "pencil": u"\uf040", - "pencil-square": u"\uf14b", - "pencil-square-o": u"\uf044", - "percent": u"\uf295", - "phone": u"\uf095", - "phone-square": u"\uf098", - "photo (alias)": u"\uf03e", - "picture-o": u"\uf03e", - "pie-chart": u"\uf200", - "pied-piper": u"\uf2ae", - "pied-piper-alt": u"\uf1a8", - "pied-piper-pp": u"\uf1a7", - "pinterest": u"\uf0d2", - "pinterest-p": u"\uf231", - "pinterest-square": u"\uf0d3", - "plane": u"\uf072", - "play": u"\uf04b", - "play-circle": u"\uf144", - "play-circle-o": u"\uf01d", - "plug": u"\uf1e6", - "plus": u"\uf067", - "plus-circle": u"\uf055", - "plus-square": u"\uf0fe", - "plus-square-o": u"\uf196", - "power-off": u"\uf011", - "print": u"\uf02f", - "product-hunt": u"\uf288", - "puzzle-piece": u"\uf12e", - "qq": u"\uf1d6", - "qrcode": u"\uf029", - "question": u"\uf128", - "question-circle": u"\uf059", - "question-circle-o": u"\uf29c", - "quote-left": u"\uf10d", - "quote-right": u"\uf10e", - "ra (alias)": u"\uf1d0", - "random": u"\uf074", - "rebel": u"\uf1d0", - "recycle": u"\uf1b8", - "reddit": u"\uf1a1", - "reddit-alien": u"\uf281", - "reddit-square": u"\uf1a2", - "refresh": u"\uf021", - "registered": u"\uf25d", - "remove (alias)": u"\uf00d", - "renren": u"\uf18b", - "reorder (alias)": u"\uf0c9", - "repeat": u"\uf01e", - "reply": u"\uf112", - "reply-all": u"\uf122", - "resistance (alias)": u"\uf1d0", - "retweet": u"\uf079", - "rmb (alias)": u"\uf157", - "road": u"\uf018", - "rocket": u"\uf135", - "rotate-left (alias)": u"\uf0e2", - "rotate-right (alias)": u"\uf01e", - "rouble (alias)": u"\uf158", - "rss": u"\uf09e", - "rss-square": u"\uf143", - "rub": u"\uf158", - "ruble (alias)": u"\uf158", - "rupee (alias)": u"\uf156", - "safari": u"\uf267", - "save (alias)": u"\uf0c7", - "scissors": u"\uf0c4", - "scribd": u"\uf28a", - "search": u"\uf002", - "search-minus": u"\uf010", - "search-plus": u"\uf00e", - "sellsy": u"\uf213", - "send (alias)": u"\uf1d8", - "send-o (alias)": u"\uf1d9", - "server": u"\uf233", - "share": u"\uf064", - "share-alt": u"\uf1e0", - "share-alt-square": u"\uf1e1", - "share-square": u"\uf14d", - "share-square-o": u"\uf045", - "shekel (alias)": u"\uf20b", - "sheqel (alias)": u"\uf20b", - "shield": u"\uf132", - "ship": u"\uf21a", - "shirtsinbulk": u"\uf214", - "shopping-bag": u"\uf290", - "shopping-basket": u"\uf291", - "shopping-cart": u"\uf07a", - "sign-in": u"\uf090", - "sign-language": u"\uf2a7", - "sign-out": u"\uf08b", - "signal": u"\uf012", - "signing (alias)": u"\uf2a7", - "simplybuilt": u"\uf215", - "sitemap": u"\uf0e8", - "skyatlas": u"\uf216", - "skype": u"\uf17e", - "slack": u"\uf198", - "sliders": u"\uf1de", - "slideshare": u"\uf1e7", - "smile-o": u"\uf118", - "snapchat": u"\uf2ab", - "snapchat-ghost": u"\uf2ac", - "snapchat-square": u"\uf2ad", - "soccer-ball-o (alias)": u"\uf1e3", - "sort": u"\uf0dc", - "sort-alpha-asc": u"\uf15d", - "sort-alpha-desc": u"\uf15e", - "sort-amount-asc": u"\uf160", - "sort-amount-desc": u"\uf161", - "sort-asc": u"\uf0de", - "sort-desc": u"\uf0dd", - "sort-down (alias)": u"\uf0dd", - "sort-numeric-asc": u"\uf162", - "sort-numeric-desc": u"\uf163", - "sort-up (alias)": u"\uf0de", - "soundcloud": u"\uf1be", - "space-shuttle": u"\uf197", - "spinner": u"\uf110", - "spoon": u"\uf1b1", - "spotify": u"\uf1bc", - "square": u"\uf0c8", - "square-o": u"\uf096", - "stack-exchange": u"\uf18d", - "stack-overflow": u"\uf16c", - "star": u"\uf005", - "star-half": u"\uf089", - "star-half-empty (alias)": u"\uf123", - "star-half-full (alias)": u"\uf123", - "star-half-o": u"\uf123", - "star-o": u"\uf006", - "steam": u"\uf1b6", - "steam-square": u"\uf1b7", - "step-backward": u"\uf048", - "step-forward": u"\uf051", - "stethoscope": u"\uf0f1", - "sticky-note": u"\uf249", - "sticky-note-o": u"\uf24a", - "stop": u"\uf04d", - "stop-circle": u"\uf28d", - "stop-circle-o": u"\uf28e", - "street-view": u"\uf21d", - "strikethrough": u"\uf0cc", - "stumbleupon": u"\uf1a4", - "stumbleupon-circle": u"\uf1a3", - "subscript": u"\uf12c", - "subway": u"\uf239", - "suitcase": u"\uf0f2", - "sun-o": u"\uf185", - "superscript": u"\uf12b", - "support (alias)": u"\uf1cd", - "table": u"\uf0ce", - "tablet": u"\uf10a", - "tachometer": u"\uf0e4", - "tag": u"\uf02b", - "tags": u"\uf02c", - "tasks": u"\uf0ae", - "taxi": u"\uf1ba", - "television": u"\uf26c", - "tencent-weibo": u"\uf1d5", - "terminal": u"\uf120", - "text-height": u"\uf034", - "text-width": u"\uf035", - "th": u"\uf00a", - "th-large": u"\uf009", - "th-list": u"\uf00b", - "themeisle": u"\uf2b2", - "thumb-tack": u"\uf08d", - "thumbs-down": u"\uf165", - "thumbs-o-down": u"\uf088", - "thumbs-o-up": u"\uf087", - "thumbs-up": u"\uf164", - "ticket": u"\uf145", - "times": u"\uf00d", - "times-circle": u"\uf057", - "times-circle-o": u"\uf05c", - "tint": u"\uf043", - "toggle-down (alias)": u"\uf150", - "toggle-left (alias)": u"\uf191", - "toggle-off": u"\uf204", - "toggle-on": u"\uf205", - "toggle-right (alias)": u"\uf152", - "toggle-up (alias)": u"\uf151", - "trademark": u"\uf25c", - "train": u"\uf238", - "transgender": u"\uf224", - "transgender-alt": u"\uf225", - "trash": u"\uf1f8", - "trash-o": u"\uf014", - "tree": u"\uf1bb", - "trello": u"\uf181", - "tripadvisor": u"\uf262", - "trophy": u"\uf091", - "truck": u"\uf0d1", - "try": u"\uf195", - "tty": u"\uf1e4", - "tumblr": u"\uf173", - "tumblr-square": u"\uf174", - "turkish-lira (alias)": u"\uf195", - "tv (alias)": u"\uf26c", - "twitch": u"\uf1e8", - "twitter": u"\uf099", - "twitter-square": u"\uf081", - "umbrella": u"\uf0e9", - "underline": u"\uf0cd", - "undo": u"\uf0e2", - "universal-access": u"\uf29a", - "university": u"\uf19c", - "unlink (alias)": u"\uf127", - "unlock": u"\uf09c", - "unlock-alt": u"\uf13e", - "unsorted (alias)": u"\uf0dc", - "upload": u"\uf093", - "usb": u"\uf287", - "usd": u"\uf155", - "user": u"\uf007", - "user-md": u"\uf0f0", - "user-plus": u"\uf234", - "user-secret": u"\uf21b", - "user-times": u"\uf235", - "users": u"\uf0c0", - "venus": u"\uf221", - "venus-double": u"\uf226", - "venus-mars": u"\uf228", - "viacoin": u"\uf237", - "viadeo": u"\uf2a9", - "viadeo-square": u"\uf2aa", - "video-camera": u"\uf03d", - "vimeo": u"\uf27d", - "vimeo-square": u"\uf194", - "vine": u"\uf1ca", - "vk": u"\uf189", - "volume-control-phone": u"\uf2a0", - "volume-down": u"\uf027", - "volume-off": u"\uf026", - "volume-up": u"\uf028", - "warning (alias)": u"\uf071", - "wechat (alias)": u"\uf1d7", - "weibo": u"\uf18a", - "weixin": u"\uf1d7", - "whatsapp": u"\uf232", - "wheelchair": u"\uf193", - "wheelchair-alt": u"\uf29b", - "wifi": u"\uf1eb", - "wikipedia-w": u"\uf266", - "windows": u"\uf17a", - "won (alias)": u"\uf159", - "wordpress": u"\uf19a", - "wpbeginner": u"\uf297", - "wpforms": u"\uf298", - "wrench": u"\uf0ad", - "xing": u"\uf168", - "xing-square": u"\uf169", - "y-combinator": u"\uf23b", - "y-combinator-square (alias)": u"\uf1d4", - "yahoo": u"\uf19e", - "yc (alias)": u"\uf23b", - "yc-square (alias)": u"\uf1d4", - "yelp": u"\uf1e9", - "yen (alias)": u"\uf157", - "yoast": u"\uf2b1", - "youtube": u"\uf167", - "youtube-play": u"\uf16a", - "youtube-square": u"\uf166" -} diff --git a/client/ayon_core/tools/pyblish_pype/constants.py b/client/ayon_core/tools/pyblish_pype/constants.py deleted file mode 100644 index 10f95ca4af..0000000000 --- a/client/ayon_core/tools/pyblish_pype/constants.py +++ /dev/null @@ -1,97 +0,0 @@ -from qtpy import QtCore - -EXPANDER_WIDTH = 20 - - -def flags(*args, **kwargs): - type_name = kwargs.pop("type_name", "Flags") - with_base = kwargs.pop("with_base", False) - enums = {} - for idx, attr_name in enumerate(args): - if with_base: - if idx == 0: - enums[attr_name] = 0 - continue - idx -= 1 - enums[attr_name] = 2**idx - - for attr_name, value in kwargs.items(): - enums[attr_name] = value - return type(type_name, (), enums) - - -def roles(*args, **kwargs): - type_name = kwargs.pop("type_name", "Roles") - enums = {} - for attr_name, value in kwargs.items(): - enums[attr_name] = value - - offset = 0 - for idx, attr_name in enumerate(args): - _idx = idx + QtCore.Qt.UserRole + offset - while _idx in enums.values(): - offset += 1 - _idx = idx + offset - - enums[attr_name] = _idx - - return type(type_name, (), enums) - - -Roles = roles( - "ObjectIdRole", - "ObjectUIdRole", - "TypeRole", - "PublishFlagsRole", - "LogRecordsRole", - - "IsOptionalRole", - "IsEnabledRole", - - "FamiliesRole", - - "DocstringRole", - "PathModuleRole", - "PluginActionsVisibleRole", - "PluginValidActionsRole", - "PluginActionProgressRole", - - "TerminalItemTypeRole", - - "IntentItemValue", - - type_name="ModelRoles" -) - -InstanceStates = flags( - "ContextType", - "InProgress", - "HasWarning", - "HasError", - "HasFinished", - type_name="InstanceState" -) - -PluginStates = flags( - "IsCompatible", - "InProgress", - "WasProcessed", - "WasSkipped", - "HasWarning", - "HasError", - type_name="PluginState" -) - -GroupStates = flags( - "HasWarning", - "HasError", - "HasFinished", - type_name="GroupStates" -) - -PluginActionStates = flags( - "InProgress", - "HasFailed", - "HasFinished", - type_name="PluginActionStates" -) diff --git a/client/ayon_core/tools/pyblish_pype/control.py b/client/ayon_core/tools/pyblish_pype/control.py deleted file mode 100644 index c5034e2736..0000000000 --- a/client/ayon_core/tools/pyblish_pype/control.py +++ /dev/null @@ -1,666 +0,0 @@ -"""The Controller in a Model/View/Controller-based application -The graphical components of Pyblish Lite use this object to perform -publishing. It communicates via the Qt Signals/Slots mechanism -and has no direct connection to any graphics. This is important, -because this is how unittests are able to run without requiring -an active window manager; such as via Travis-CI. -""" -import os -import sys -import inspect -import logging -import collections - -from qtpy import QtCore - -import pyblish.api -import pyblish.util -import pyblish.logic -import pyblish.lib -import pyblish.version - -from . import util -from .constants import InstanceStates - -from ayon_core.settings import get_current_project_settings - - -class IterationBreak(Exception): - pass - - -class MainThreadItem: - """Callback with args and kwargs.""" - def __init__(self, callback, *args, **kwargs): - self.callback = callback - self.args = args - self.kwargs = kwargs - - def process(self): - self.callback(*self.args, **self.kwargs) - - -class MainThreadProcess(QtCore.QObject): - """Qt based main thread process executor. - - Has timer which controls each 50ms if there is new item to process. - - This approach gives ability to update UI meanwhile plugin is in progress. - """ - # How many times let pass QtApplication to process events - # - use 2 as resize event can trigger repaint event but not process in - # same loop - count_timeout = 2 - - def __init__(self): - super(MainThreadProcess, self).__init__() - self._items_to_process = collections.deque() - - timer = QtCore.QTimer() - timer.setInterval(0) - - timer.timeout.connect(self._execute) - - self._timer = timer - self._switch_counter = self.count_timeout - - def process(self, func, *args, **kwargs): - item = MainThreadItem(func, *args, **kwargs) - self.add_item(item) - - def add_item(self, item): - self._items_to_process.append(item) - - def _execute(self): - if not self._items_to_process: - return - - if self._switch_counter > 0: - self._switch_counter -= 1 - return - - self._switch_counter = self.count_timeout - - item = self._items_to_process.popleft() - item.process() - - def start(self): - if not self._timer.isActive(): - self._timer.start() - - def stop(self): - if self._timer.isActive(): - self._timer.stop() - - def clear(self): - if self._timer.isActive(): - self._timer.stop() - self._items_to_process = collections.deque() - - def stop_if_empty(self): - if self._timer.isActive(): - item = MainThreadItem(self._stop_if_empty) - self.add_item(item) - - def _stop_if_empty(self): - if not self._items_to_process: - self.stop() - - -class Controller(QtCore.QObject): - log = logging.getLogger("PyblishController") - # Emitted when the GUI is about to start processing; - # e.g. resetting, validating or publishing. - about_to_process = QtCore.Signal(object, object) - - # ??? Emitted for each process - was_processed = QtCore.Signal(dict) - - # Emitted when reset - # - all data are reset (plugins, processing, pari yielder, etc.) - was_reset = QtCore.Signal() - - # Emitted when previous group changed - passed_group = QtCore.Signal(object) - - # Emitted when want to change state of instances - switch_toggleability = QtCore.Signal(bool) - - # On action finished - was_acted = QtCore.Signal(dict) - - # Emitted when processing has stopped - was_stopped = QtCore.Signal() - - # Emitted when processing has finished - was_finished = QtCore.Signal() - - # Emitted when plugin was skipped - was_skipped = QtCore.Signal(object) - - # store OrderGroups - now it is a singleton - order_groups = util.OrderGroups - - # When instance is toggled - instance_toggled = QtCore.Signal(object, object, object) - - def __init__(self, parent=None): - super(Controller, self).__init__(parent) - self.context = None - self.plugins = {} - self.optional_default = {} - self.instance_toggled.connect(self._on_instance_toggled) - self._main_thread_processor = MainThreadProcess() - - self._current_state = "" - - def reset_variables(self): - self.log.debug("Resetting pyblish context variables") - - # Data internal to the GUI itself - self.is_running = False - self.stopped = False - self.errored = False - self._current_state = "" - - # Active producer of pairs - self.pair_generator = None - # Active pair - self.current_pair = None - - # Orders which changes GUI - # - passing collectors order disables plugin/instance toggle - self.collect_state = 0 - - # - passing validators order disables validate button and gives ability - # to know when to stop on validate button press - self.validators_order = None - self.validated = False - - # Get collectors and validators order - plugin_groups_keys = list(self.order_groups.groups.keys()) - self.validators_order = self.order_groups.validation_order - next_group_order = None - if len(plugin_groups_keys) > 1: - next_group_order = plugin_groups_keys[1] - - # This is used to track whether or not to continue - # processing when, for example, validation has failed. - self.processing = { - "stop_on_validation": False, - # Used? - "last_plugin_order": None, - "current_group_order": plugin_groups_keys[0], - "next_group_order": next_group_order, - "nextOrder": None, - "ordersWithError": set() - } - self._set_state_by_order() - self.log.debug("Reset of pyblish context variables done") - - @property - def current_state(self): - return self._current_state - - @staticmethod - def _convert_filter_presets(filter_presets): - """Convert AYON settings presets to dictionary. - - Returns: - dict[str, dict[str, Any]]: Filter presets converted to dictionary. - """ - if not isinstance(filter_presets, list): - return filter_presets - - return { - filter_preset["name"]: { - item["name"]: item["value"] - for item in filter_preset["value"] - } - for filter_preset in filter_presets - } - - def presets_by_hosts(self): - # Get global filters as base - presets = get_current_project_settings() - if not presets: - return {} - - result = {} - hosts = pyblish.api.registered_hosts() - for host in hosts: - host_presets = presets.get(host, {}).get("filters") - if not host_presets: - continue - - host_presets = self._convert_filter_presets(host_presets) - - for key, value in host_presets.items(): - if value is None: - if key in result: - result.pop(key) - continue - - result[key] = value - - return result - - def reset_context(self): - self.log.debug("Resetting pyblish context object") - - comment = None - if ( - self.context is not None and - self.context.data.get("comment") and - # We only preserve the user typed comment if we are *not* - # resetting from a successful publish without errors - self._current_state != "Published" - ): - comment = self.context.data["comment"] - - self.context = pyblish.api.Context() - - self.context._publish_states = InstanceStates.ContextType - self.context.optional = False - - self.context.data["publish"] = True - self.context.data["name"] = "context" - - self.context.data["host"] = reversed(pyblish.api.registered_hosts()) - self.context.data["port"] = int( - os.environ.get("PYBLISH_CLIENT_PORT", -1) - ) - self.context.data["connectTime"] = pyblish.lib.time(), - self.context.data["pyblishVersion"] = pyblish.version, - self.context.data["pythonVersion"] = sys.version - - self.context.data["icon"] = "book" - - self.context.families = ("__context__",) - - if comment: - # Preserve comment on reset if user previously had a comment - self.context.data["comment"] = comment - - self.log.debug("Reset of pyblish context object done") - - def reset(self): - """Discover plug-ins and run collection.""" - self._main_thread_processor.clear() - self._main_thread_processor.process(self._reset) - self._main_thread_processor.start() - - def _reset(self): - self.reset_context() - self.reset_variables() - - self.possible_presets = self.presets_by_hosts() - - # Load plugins and set pair generator - self.load_plugins() - self.pair_generator = self._pair_yielder(self.plugins) - - self.was_reset.emit() - - # Process collectors load rest of plugins with collected instances - self.collect() - - def load_plugins(self): - self.test = pyblish.logic.registered_test() - self.optional_default = {} - - plugins = pyblish.api.discover() - - targets = set(pyblish.logic.registered_targets()) - targets.add("default") - targets = list(targets) - plugins_by_targets = pyblish.logic.plugins_by_targets(plugins, targets) - - _plugins = [] - for plugin in plugins_by_targets: - # Skip plugin if is not optional and not active - if ( - not getattr(plugin, "optional", False) - and not getattr(plugin, "active", True) - ): - continue - _plugins.append(plugin) - self.plugins = _plugins - - def on_published(self): - if self.is_running: - self.is_running = False - self._current_state = ( - "Published" if not self.errored else "Published, with errors" - ) - self.was_finished.emit() - self._main_thread_processor.stop() - - def stop(self): - self.log.debug("Stopping") - self.stopped = True - - def act(self, plugin, action): - self.is_running = True - item = MainThreadItem(self._process_action, plugin, action) - self._main_thread_processor.add_item(item) - self._main_thread_processor.start() - self._main_thread_processor.stop_if_empty() - - def _process_action(self, plugin, action): - result = pyblish.plugin.process( - plugin, self.context, None, action.id - ) - self.is_running = False - self.was_acted.emit(result) - - def emit_(self, signal, kwargs): - pyblish.api.emit(signal, **kwargs) - - def _process(self, plugin, instance=None): - """Produce `result` from `plugin` and `instance` - :func:`process` shares state with :func:`_iterator` such that - an instance/plugin pair can be fetched and processed in isolation. - Arguments: - plugin (pyblish.api.Plugin): Produce result using plug-in - instance (optional, pyblish.api.Instance): Process this instance, - if no instance is provided, context is processed. - """ - - self.processing["nextOrder"] = plugin.order - - try: - result = pyblish.plugin.process(plugin, self.context, instance) - # Make note of the order at which the - # potential error error occurred. - if result["error"] is not None: - self.processing["ordersWithError"].add(plugin.order) - - except Exception as exc: - raise Exception("Unknown error({}): {}".format( - plugin.__name__, str(exc) - )) - - return result - - def _pair_yielder(self, plugins): - for plugin in plugins: - if ( - self.processing["current_group_order"] is not None - and plugin.order > self.processing["current_group_order"] - ): - current_group_order = self.processing["current_group_order"] - - new_next_group_order = None - new_current_group_order = self.processing["next_group_order"] - if new_current_group_order is not None: - current_next_order_found = False - for order in self.order_groups.groups.keys(): - if current_next_order_found: - new_next_group_order = order - break - - if order == new_current_group_order: - current_next_order_found = True - - self.processing["next_group_order"] = new_next_group_order - self.processing["current_group_order"] = ( - new_current_group_order - ) - - # Force update to the current state - self._set_state_by_order() - - if self.collect_state == 0: - self.collect_state = 1 - self._current_state = ( - "Ready" if not self.errored else - "Collected, with errors" - ) - self.switch_toggleability.emit(True) - self.passed_group.emit(current_group_order) - yield IterationBreak("Collected") - - else: - self.passed_group.emit(current_group_order) - if self.errored: - self._current_state = ( - "Stopped, due to errors" if not - self.processing["stop_on_validation"] else - "Validated, with errors" - ) - yield IterationBreak("Last group errored") - - if self.collect_state == 1: - self.collect_state = 2 - self.switch_toggleability.emit(False) - - if not self.validated and plugin.order > self.validators_order: - self.validated = True - if self.processing["stop_on_validation"]: - self._current_state = ( - "Validated" if not self.errored else - "Validated, with errors" - ) - yield IterationBreak("Validated") - - # Stop if was stopped - if self.stopped: - self.stopped = False - self._current_state = "Paused" - yield IterationBreak("Stopped") - - # check test if will stop - self.processing["nextOrder"] = plugin.order - message = self.test(**self.processing) - if message: - self._current_state = "Paused" - yield IterationBreak("Stopped due to \"{}\"".format(message)) - - self.processing["last_plugin_order"] = plugin.order - if not plugin.active: - pyblish.logic.log.debug("%s was inactive, skipping.." % plugin) - self.was_skipped.emit(plugin) - continue - - in_collect_stage = self.collect_state == 0 - if plugin.__instanceEnabled__: - instances = pyblish.logic.instances_by_plugin( - self.context, plugin - ) - if not instances: - self.was_skipped.emit(plugin) - continue - - for instance in instances: - if ( - not in_collect_stage - and instance.data.get("publish") is False - ): - pyblish.logic.log.debug( - "%s was inactive, skipping.." % instance - ) - continue - # Stop if was stopped - if self.stopped: - self.stopped = False - self._current_state = "Paused" - yield IterationBreak("Stopped") - - yield (plugin, instance) - else: - families = util.collect_families_from_instances( - self.context, only_active=not in_collect_stage - ) - plugins = pyblish.logic.plugins_by_families( - [plugin], families - ) - if not plugins: - self.was_skipped.emit(plugin) - continue - yield (plugin, None) - - self.passed_group.emit(self.processing["next_group_order"]) - - def iterate_and_process(self, on_finished=None): - """ Iterating inserted plugins with current context. - Collectors do not contain instances, they are None when collecting! - This process don't stop on one - """ - self._main_thread_processor.start() - - def on_next(): - self.log.debug("Looking for next pair to process") - try: - self.current_pair = next(self.pair_generator) - if isinstance(self.current_pair, IterationBreak): - raise self.current_pair - - except IterationBreak: - self.log.debug("Iteration break was raised") - self.is_running = False - self.was_stopped.emit() - self._main_thread_processor.stop() - return - - except StopIteration: - self.log.debug("Iteration stop was raised") - self.is_running = False - # All pairs were processed successfully! - if on_finished is not None: - self._main_thread_processor.add_item( - MainThreadItem(on_finished) - ) - self._main_thread_processor.stop_if_empty() - return - - except Exception as exc: - self.log.warning( - "Unexpected exception during `on_next` happened", - exc_info=True - ) - exc_msg = str(exc) - self._main_thread_processor.add_item( - MainThreadItem(on_unexpected_error, error=exc_msg) - ) - return - - self.about_to_process.emit(*self.current_pair) - self._main_thread_processor.add_item( - MainThreadItem(on_process) - ) - - def on_process(): - try: - self.log.debug( - "Processing pair: {}".format(str(self.current_pair)) - ) - result = self._process(*self.current_pair) - if result["error"] is not None: - self.log.debug("Error happened") - self.errored = True - - self.log.debug("Pair processed") - self.was_processed.emit(result) - - except Exception as exc: - self.log.warning( - "Unexpected exception during `on_process` happened", - exc_info=True - ) - exc_msg = str(exc) - self._main_thread_processor.add_item( - MainThreadItem(on_unexpected_error, error=exc_msg) - ) - return - - self._main_thread_processor.add_item( - MainThreadItem(on_next) - ) - - def on_unexpected_error(error): - # TODO this should be handled much differently - # TODO emit crash signal to show message box with traceback? - self.is_running = False - self.was_stopped.emit() - util.u_print(u"An unexpected error occurred:\n %s" % error) - if on_finished is not None: - self._main_thread_processor.add_item( - MainThreadItem(on_finished) - ) - self._main_thread_processor.stop_if_empty() - - self.is_running = True - self._main_thread_processor.add_item( - MainThreadItem(on_next) - ) - - def _set_state_by_order(self): - order = self.processing["current_group_order"] - self._current_state = self.order_groups.groups[order]["state"] - - def collect(self): - """ Iterate and process Collect plugins - - load_plugins method is launched again when finished - """ - self._set_state_by_order() - self._main_thread_processor.process(self._start_collect) - self._main_thread_processor.start() - - def validate(self): - """ Process plugins to validations_order value.""" - self._set_state_by_order() - self._main_thread_processor.process(self._start_validate) - self._main_thread_processor.start() - - def publish(self): - """ Iterate and process all remaining plugins.""" - self._set_state_by_order() - self._main_thread_processor.process(self._start_publish) - self._main_thread_processor.start() - - def _start_collect(self): - self.iterate_and_process() - - def _start_validate(self): - self.processing["stop_on_validation"] = True - self.iterate_and_process() - - def _start_publish(self): - self.processing["stop_on_validation"] = False - self.iterate_and_process(self.on_published) - - def cleanup(self): - """Forcefully delete objects from memory - In an ideal world, this shouldn't be necessary. Garbage - collection guarantees that anything without reference - is automatically removed. - However, because this application is designed to be run - multiple times from the same interpreter process, extra - case must be taken to ensure there are no memory leaks. - Explicitly deleting objects shines a light on where objects - may still be referenced in the form of an error. No errors - means this was unnecessary, but that's ok. - """ - - for instance in self.context: - del(instance) - - for plugin in self.plugins: - del(plugin) - - def _on_instance_toggled(self, instance, old_value, new_value): - callbacks = pyblish.api.registered_callbacks().get("instanceToggled") - if not callbacks: - return - - for callback in callbacks: - try: - callback(instance, old_value, new_value) - except Exception: - self.log.warning( - "Callback for `instanceToggled` crashed. {}".format( - os.path.abspath(inspect.getfile(callback)) - ), - exc_info=True - ) diff --git a/client/ayon_core/tools/pyblish_pype/delegate.py b/client/ayon_core/tools/pyblish_pype/delegate.py deleted file mode 100644 index bb253dd1a3..0000000000 --- a/client/ayon_core/tools/pyblish_pype/delegate.py +++ /dev/null @@ -1,540 +0,0 @@ -import platform - -from qtpy import QtWidgets, QtGui, QtCore - -from . import model -from .awesome import tags as awesome -from .constants import ( - PluginStates, InstanceStates, PluginActionStates, Roles, EXPANDER_WIDTH -) - -colors = { - "error": QtGui.QColor("#ff4a4a"), - "warning": QtGui.QColor("#ff9900"), - "ok": QtGui.QColor("#77AE24"), - "active": QtGui.QColor("#99CEEE"), - "idle": QtCore.Qt.white, - "inactive": QtGui.QColor("#888"), - "hover": QtGui.QColor(255, 255, 255, 10), - "selected": QtGui.QColor(255, 255, 255, 20), - "outline": QtGui.QColor("#333"), - "group": QtGui.QColor("#333"), - "group-hover": QtGui.QColor("#3c3c3c"), - "group-selected-hover": QtGui.QColor("#555555"), - "expander-bg": QtGui.QColor("#222"), - "expander-hover": QtGui.QColor("#2d6c9f"), - "expander-selected-hover": QtGui.QColor("#3784c5") -} - -scale_factors = {"darwin": 1.5} -scale_factor = scale_factors.get(platform.system().lower(), 1.0) -fonts = { - "h3": QtGui.QFont("Open Sans", 10 * scale_factor, QtGui.QFont.Normal), - "h4": QtGui.QFont("Open Sans", 8 * scale_factor, QtGui.QFont.Normal), - "h5": QtGui.QFont("Open Sans", 8 * scale_factor, QtGui.QFont.DemiBold), - "awesome6": QtGui.QFont("FontAwesome", 6 * scale_factor), - "awesome10": QtGui.QFont("FontAwesome", 10 * scale_factor), - "smallAwesome": QtGui.QFont("FontAwesome", 8 * scale_factor), - "largeAwesome": QtGui.QFont("FontAwesome", 16 * scale_factor), -} -font_metrics = { - "awesome6": QtGui.QFontMetrics(fonts["awesome6"]), - "h4": QtGui.QFontMetrics(fonts["h4"]), - "h5": QtGui.QFontMetrics(fonts["h5"]) -} -icons = { - "action": awesome["adn"], - "angle-right": awesome["angle-right"], - "angle-left": awesome["angle-left"], - "plus-sign": awesome['plus'], - "minus-sign": awesome['minus'] -} - - -class PluginItemDelegate(QtWidgets.QStyledItemDelegate): - """Generic delegate for model items""" - - def paint(self, painter, option, index): - """Paint checkbox and text. - _ - |_| My label > - """ - - body_rect = QtCore.QRectF(option.rect) - - check_rect = QtCore.QRectF(body_rect) - check_rect.setWidth(check_rect.height()) - check_offset = (check_rect.height() / 4) + 1 - check_rect.adjust( - check_offset, check_offset, -check_offset, -check_offset - ) - - check_color = colors["idle"] - - perspective_icon = icons["angle-right"] - perspective_rect = QtCore.QRectF(body_rect) - perspective_rect.setWidth(perspective_rect.height()) - perspective_rect.adjust(0, 3, 0, 0) - perspective_rect.translate( - body_rect.width() - (perspective_rect.width() / 2 + 2), - 0 - ) - - publish_states = index.data(Roles.PublishFlagsRole) - if publish_states & PluginStates.InProgress: - check_color = colors["active"] - - elif publish_states & PluginStates.HasError: - check_color = colors["error"] - - elif publish_states & PluginStates.HasWarning: - check_color = colors["warning"] - - elif publish_states & PluginStates.WasProcessed: - check_color = colors["ok"] - - elif not index.data(Roles.IsEnabledRole): - check_color = colors["inactive"] - - offset = (body_rect.height() - font_metrics["h4"].height()) / 2 - label_rect = QtCore.QRectF(body_rect.adjusted( - check_rect.width() + 12, offset - 1, 0, 0 - )) - - assert label_rect.width() > 0 - - label = index.data(QtCore.Qt.DisplayRole) - label = font_metrics["h4"].elidedText( - label, - QtCore.Qt.ElideRight, - label_rect.width() - 20 - ) - - font_color = colors["idle"] - if not index.data(QtCore.Qt.CheckStateRole): - font_color = colors["inactive"] - - # Maintain reference to state, so we can restore it once we're done - painter.save() - - # Draw perspective icon - painter.setFont(fonts["awesome10"]) - painter.setPen(QtGui.QPen(font_color)) - painter.drawText(perspective_rect, perspective_icon) - - # Draw label - painter.setFont(fonts["h4"]) - painter.setPen(QtGui.QPen(font_color)) - painter.drawText(label_rect, label) - - # Draw action icon - if index.data(Roles.PluginActionsVisibleRole): - painter.save() - action_state = index.data(Roles.PluginActionProgressRole) - if action_state & PluginActionStates.HasFailed: - color = colors["error"] - elif action_state & PluginActionStates.HasFinished: - color = colors["ok"] - elif action_state & PluginActionStates.InProgress: - color = colors["active"] - else: - color = colors["idle"] - - painter.setFont(fonts["smallAwesome"]) - painter.setPen(QtGui.QPen(color)) - - icon_rect = QtCore.QRectF( - option.rect.adjusted( - label_rect.width() - perspective_rect.width() / 2, - label_rect.height() / 3, 0, 0 - ) - ) - painter.drawText(icon_rect, icons["action"]) - - painter.restore() - - # Draw checkbox - pen = QtGui.QPen(check_color, 1) - painter.setPen(pen) - - if index.data(Roles.IsOptionalRole): - painter.drawRect(check_rect) - - if index.data(QtCore.Qt.CheckStateRole): - optional_check_rect = QtCore.QRectF(check_rect) - optional_check_rect.adjust(2, 2, -1, -1) - painter.fillRect(optional_check_rect, check_color) - - else: - painter.fillRect(check_rect, check_color) - - if option.state & QtWidgets.QStyle.State_MouseOver: - painter.fillRect(body_rect, colors["hover"]) - - if option.state & QtWidgets.QStyle.State_Selected: - painter.fillRect(body_rect, colors["selected"]) - - # Ok, we're done, tidy up. - painter.restore() - - def sizeHint(self, option, index): - return QtCore.QSize(option.rect.width(), 20) - - -class InstanceItemDelegate(QtWidgets.QStyledItemDelegate): - """Generic delegate for model items""" - - def paint(self, painter, option, index): - """Paint checkbox and text. - _ - |_| My label > - """ - - body_rect = QtCore.QRectF(option.rect) - - check_rect = QtCore.QRectF(body_rect) - check_rect.setWidth(check_rect.height()) - offset = (check_rect.height() / 4) + 1 - check_rect.adjust(offset, offset, -(offset), -(offset)) - - check_color = colors["idle"] - - perspective_icon = icons["angle-right"] - perspective_rect = QtCore.QRectF(body_rect) - perspective_rect.setWidth(perspective_rect.height()) - perspective_rect.adjust(0, 3, 0, 0) - perspective_rect.translate( - body_rect.width() - (perspective_rect.width() / 2 + 2), - 0 - ) - - publish_states = index.data(Roles.PublishFlagsRole) - if publish_states & InstanceStates.InProgress: - check_color = colors["active"] - - elif publish_states & InstanceStates.HasError: - check_color = colors["error"] - - elif publish_states & InstanceStates.HasWarning: - check_color = colors["warning"] - - elif publish_states & InstanceStates.HasFinished: - check_color = colors["ok"] - - elif not index.data(Roles.IsEnabledRole): - check_color = colors["inactive"] - - offset = (body_rect.height() - font_metrics["h4"].height()) / 2 - label_rect = QtCore.QRectF(body_rect.adjusted( - check_rect.width() + 12, offset - 1, 0, 0 - )) - - assert label_rect.width() > 0 - - label = index.data(QtCore.Qt.DisplayRole) - label = font_metrics["h4"].elidedText( - label, - QtCore.Qt.ElideRight, - label_rect.width() - 20 - ) - - font_color = colors["idle"] - if not index.data(QtCore.Qt.CheckStateRole): - font_color = colors["inactive"] - - # Maintain reference to state, so we can restore it once we're done - painter.save() - - # Draw perspective icon - painter.setFont(fonts["awesome10"]) - painter.setPen(QtGui.QPen(font_color)) - painter.drawText(perspective_rect, perspective_icon) - - # Draw label - painter.setFont(fonts["h4"]) - painter.setPen(QtGui.QPen(font_color)) - painter.drawText(label_rect, label) - - # Draw checkbox - pen = QtGui.QPen(check_color, 1) - painter.setPen(pen) - - if index.data(Roles.IsOptionalRole): - painter.drawRect(check_rect) - - if index.data(QtCore.Qt.CheckStateRole): - optional_check_rect = QtCore.QRectF(check_rect) - optional_check_rect.adjust(2, 2, -1, -1) - painter.fillRect(optional_check_rect, check_color) - - else: - painter.fillRect(check_rect, check_color) - - if option.state & QtWidgets.QStyle.State_MouseOver: - painter.fillRect(body_rect, colors["hover"]) - - if option.state & QtWidgets.QStyle.State_Selected: - painter.fillRect(body_rect, colors["selected"]) - - # Ok, we're done, tidy up. - painter.restore() - - def sizeHint(self, option, index): - return QtCore.QSize(option.rect.width(), 20) - - -class InstanceDelegate(QtWidgets.QStyledItemDelegate): - """Generic delegate for instance header""" - - radius = 8.0 - - def __init__(self, parent): - super(InstanceDelegate, self).__init__(parent) - self.item_delegate = InstanceItemDelegate(parent) - - def paint(self, painter, option, index): - if index.data(Roles.TypeRole) in ( - model.InstanceType, model.PluginType - ): - self.item_delegate.paint(painter, option, index) - return - - self.group_item_paint(painter, option, index) - - def group_item_paint(self, painter, option, index): - """Paint text - _ - My label - """ - body_rect = QtCore.QRectF(option.rect) - bg_rect = QtCore.QRectF( - body_rect.left(), body_rect.top() + 1, - body_rect.width() - 5, body_rect.height() - 2 - ) - - expander_rect = QtCore.QRectF(bg_rect) - expander_rect.setWidth(EXPANDER_WIDTH) - - remainder_rect = QtCore.QRectF( - expander_rect.x() + expander_rect.width(), - expander_rect.y(), - bg_rect.width() - expander_rect.width(), - expander_rect.height() - ) - - width = float(expander_rect.width()) - height = float(expander_rect.height()) - - x_pos = expander_rect.x() - y_pos = expander_rect.y() - - x_radius = min(self.radius, width / 2) - y_radius = min(self.radius, height / 2) - x_radius2 = x_radius * 2 - y_radius2 = y_radius * 2 - - expander_path = QtGui.QPainterPath() - expander_path.moveTo(x_pos, y_pos + y_radius) - expander_path.arcTo( - x_pos, y_pos, - x_radius2, y_radius2, - 180.0, -90.0 - ) - expander_path.lineTo(x_pos + width, y_pos) - expander_path.lineTo(x_pos + width, y_pos + height) - expander_path.lineTo(x_pos + x_radius, y_pos + height) - expander_path.arcTo( - x_pos, y_pos + height - y_radius2, - x_radius2, y_radius2, - 270.0, -90.0 - ) - expander_path.closeSubpath() - - width = float(remainder_rect.width()) - height = float(remainder_rect.height()) - x_pos = remainder_rect.x() - y_pos = remainder_rect.y() - - x_radius = min(self.radius, width / 2) - y_radius = min(self.radius, height / 2) - x_radius2 = x_radius * 2 - y_radius2 = y_radius * 2 - - remainder_path = QtGui.QPainterPath() - remainder_path.moveTo(x_pos + width, y_pos + height - y_radius) - remainder_path.arcTo( - x_pos + width - x_radius2, y_pos + height - y_radius2, - x_radius2, y_radius2, - 0.0, -90.0 - ) - remainder_path.lineTo(x_pos, y_pos + height) - remainder_path.lineTo(x_pos, y_pos) - remainder_path.lineTo(x_pos + width - x_radius, y_pos) - remainder_path.arcTo( - x_pos + width - x_radius2, y_pos, - x_radius2, y_radius2, - 90.0, -90.0 - ) - remainder_path.closeSubpath() - - painter.fillPath(expander_path, colors["expander-bg"]) - painter.fillPath(remainder_path, colors["group"]) - - mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) - selected = option.state & QtWidgets.QStyle.State_Selected - hovered = option.state & QtWidgets.QStyle.State_MouseOver - - if selected and hovered: - if expander_rect.contains(mouse_pos): - painter.fillPath( - expander_path, colors["expander-selected-hover"] - ) - else: - painter.fillPath( - remainder_path, colors["group-selected-hover"] - ) - - elif hovered: - if expander_rect.contains(mouse_pos): - painter.fillPath(expander_path, colors["expander-hover"]) - else: - painter.fillPath(remainder_path, colors["group-hover"]) - - text_height = font_metrics["awesome6"].height() - adjust_value = (expander_rect.height() - text_height) / 2 - expander_rect.adjust( - adjust_value + 1.5, adjust_value - 0.5, - -adjust_value + 1.5, -adjust_value - 0.5 - ) - - offset = (remainder_rect.height() - font_metrics["h5"].height()) / 2 - label_rect = QtCore.QRectF(remainder_rect.adjusted( - 5, offset - 1, 0, 0 - )) - - expander_icon = icons["plus-sign"] - - expanded = self.parent().isExpanded(index) - if expanded: - expander_icon = icons["minus-sign"] - label = index.data(QtCore.Qt.DisplayRole) - label = font_metrics["h5"].elidedText( - label, QtCore.Qt.ElideRight, label_rect.width() - ) - - # Maintain reference to state, so we can restore it once we're done - painter.save() - - painter.setFont(fonts["awesome6"]) - painter.setPen(QtGui.QPen(colors["idle"])) - painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) - - # Draw label - painter.setFont(fonts["h5"]) - painter.drawText(label_rect, label) - - # Ok, we're done, tidy up. - painter.restore() - - def sizeHint(self, option, index): - return QtCore.QSize(option.rect.width(), 20) - - -class PluginDelegate(QtWidgets.QStyledItemDelegate): - """Generic delegate for plugin header""" - - def __init__(self, parent): - super(PluginDelegate, self).__init__(parent) - self.item_delegate = PluginItemDelegate(parent) - - def paint(self, painter, option, index): - if index.data(Roles.TypeRole) in ( - model.InstanceType, model.PluginType - ): - self.item_delegate.paint(painter, option, index) - return - - self.group_item_paint(painter, option, index) - - def group_item_paint(self, painter, option, index): - """Paint text - _ - My label - """ - body_rect = QtCore.QRectF(option.rect) - bg_rect = QtCore.QRectF( - body_rect.left(), body_rect.top() + 1, - body_rect.width() - 5, body_rect.height() - 2 - ) - radius = 8.0 - bg_path = QtGui.QPainterPath() - bg_path.addRoundedRect(bg_rect, radius, radius) - hovered = option.state & QtWidgets.QStyle.State_MouseOver - selected = option.state & QtWidgets.QStyle.State_Selected - if hovered and selected: - painter.fillPath(bg_path, colors["group-selected-hover"]) - elif hovered: - painter.fillPath(bg_path, colors["group-hover"]) - else: - painter.fillPath(bg_path, colors["group"]) - - expander_rect = QtCore.QRectF(bg_rect) - expander_rect.setWidth(expander_rect.height()) - text_height = font_metrics["awesome6"].height() - adjust_value = (expander_rect.height() - text_height) / 2 - expander_rect.adjust( - adjust_value + 1.5, adjust_value - 0.5, - -adjust_value + 1.5, -adjust_value - 0.5 - ) - - offset = (bg_rect.height() - font_metrics["h5"].height()) / 2 - label_rect = QtCore.QRectF(bg_rect.adjusted( - expander_rect.width() + 12, offset - 1, 0, 0 - )) - - assert label_rect.width() > 0 - - expander_icon = icons["plus-sign"] - - expanded = self.parent().isExpanded(index) - if expanded: - expander_icon = icons["minus-sign"] - label = index.data(QtCore.Qt.DisplayRole) - label = font_metrics["h5"].elidedText( - label, QtCore.Qt.ElideRight, label_rect.width() - ) - - # Maintain reference to state, so we can restore it once we're done - painter.save() - - painter.setFont(fonts["awesome6"]) - painter.setPen(QtGui.QPen(colors["idle"])) - painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) - - # Draw label - painter.setFont(fonts["h5"]) - painter.drawText(label_rect, label) - - # Ok, we're done, tidy up. - painter.restore() - - def sizeHint(self, option, index): - return QtCore.QSize(option.rect.width(), 20) - - -class TerminalItem(QtWidgets.QStyledItemDelegate): - """Delegate used exclusively for the Terminal""" - - def paint(self, painter, option, index): - super(TerminalItem, self).paint(painter, option, index) - item_type = index.data(Roles.TypeRole) - if item_type == model.TerminalDetailType: - return - - hover = QtGui.QPainterPath() - hover.addRect(QtCore.QRectF(option.rect).adjusted(0, 0, -1, -1)) - if option.state & QtWidgets.QStyle.State_Selected: - painter.fillPath(hover, colors["selected"]) - - if option.state & QtWidgets.QStyle.State_MouseOver: - painter.fillPath(hover, colors["hover"]) diff --git a/client/ayon_core/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf b/client/ayon_core/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf deleted file mode 100644 index 9d02852c14..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/LICENSE.txt b/client/ayon_core/tools/pyblish_pype/font/opensans/LICENSE.txt deleted file mode 100644 index d645695673..0000000000 --- a/client/ayon_core/tools/pyblish_pype/font/opensans/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf deleted file mode 100644 index fd79d43bea..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf deleted file mode 100644 index 9bc800958a..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf deleted file mode 100644 index 21f6f84a07..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf deleted file mode 100644 index 31cb688340..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf deleted file mode 100644 index c90da48ff3..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf deleted file mode 100644 index 0d381897da..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf deleted file mode 100644 index 68299c4bc6..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf deleted file mode 100644 index db433349b7..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf deleted file mode 100644 index 1a7679e394..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf b/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf deleted file mode 100644 index 59b6d16b06..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/i18n/pyblish_lite.pro b/client/ayon_core/tools/pyblish_pype/i18n/pyblish_lite.pro deleted file mode 100644 index c8e2a5b56f..0000000000 --- a/client/ayon_core/tools/pyblish_pype/i18n/pyblish_lite.pro +++ /dev/null @@ -1,2 +0,0 @@ -SOURCES = ../window.py -TRANSLATIONS = zh_CN.ts \ No newline at end of file diff --git a/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.qm b/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.qm deleted file mode 100644 index fed08d8a51..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.qm and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.ts b/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.ts deleted file mode 100644 index 18ba81f69f..0000000000 --- a/client/ayon_core/tools/pyblish_pype/i18n/zh_CN.ts +++ /dev/null @@ -1,96 +0,0 @@ - - - - Window - - - Finishing up reset.. - 完成重置.. - - - - Comment.. - 备注.. - - - - Processing - 处理 - - - - Stopped due to error(s), see Terminal. - 因错误终止, 请查看终端。 - - - - Finished successfully! - 成功完成! - - - - About to reset.. - 即将重置.. - - - - Preparing validate.. - 准备校验.. - - - - Preparing publish.. - 准备发布.. - - - - Preparing - 准备 - - - - Action prepared. - 动作已就绪。 - - - - Cleaning up models.. - 清理数据模型.. - - - - Cleaning up terminal.. - 清理终端.. - - - - Cleaning up controller.. - 清理控制器.. - - - - All clean! - 清理完成! - - - - Good bye - 再见 - - - - ..as soon as processing is finished.. - ..处理即将完成.. - - - - Stopping.. - 正在停止.. - - - - Closing.. - 正在关闭.. - - - diff --git a/client/ayon_core/tools/pyblish_pype/img/down_arrow.png b/client/ayon_core/tools/pyblish_pype/img/down_arrow.png deleted file mode 100644 index e271f7f90b..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/img/down_arrow.png and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/img/logo-extrasmall.png b/client/ayon_core/tools/pyblish_pype/img/logo-extrasmall.png deleted file mode 100644 index ebe45c4c6e..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/img/logo-extrasmall.png and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/img/tab-overview.png b/client/ayon_core/tools/pyblish_pype/img/tab-overview.png deleted file mode 100644 index 443a750a7c..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/img/tab-overview.png and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/img/tab-terminal.png b/client/ayon_core/tools/pyblish_pype/img/tab-terminal.png deleted file mode 100644 index ea1bcff98d..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/img/tab-terminal.png and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/mock.py b/client/ayon_core/tools/pyblish_pype/mock.py deleted file mode 100644 index c85ff0f2ba..0000000000 --- a/client/ayon_core/tools/pyblish_pype/mock.py +++ /dev/null @@ -1,732 +0,0 @@ -import os -import time -import subprocess - -import pyblish.api - - -class MyAction(pyblish.api.Action): - label = "My Action" - on = "processed" - - def process(self, context, plugin): - self.log.info("Running!") - - -class MyOtherAction(pyblish.api.Action): - label = "My Other Action" - - def process(self, context, plugin): - self.log.info("Running!") - - -class CollectComment(pyblish.api.ContextPlugin): - """This collector has a very long comment. - - The idea is that this comment should either be elided, or word- - wrapped in the corresponding view. - - """ - - order = pyblish.api.CollectorOrder - - def process(self, context): - context.data["comment"] = "" - - -class MyCollector(pyblish.api.ContextPlugin): - label = "My Collector" - order = pyblish.api.CollectorOrder - - def process(self, context): - context.create_instance("MyInstance 1", families=["myFamily"]) - context.create_instance("MyInstance 2", families=["myFamily 2"]) - context.create_instance( - "MyInstance 3", - families=["myFamily 2"], - publish=False - ) - - -class MyValidator(pyblish.api.InstancePlugin): - order = pyblish.api.ValidatorOrder - active = False - label = "My Validator" - actions = [MyAction, - MyOtherAction] - - def process(self, instance): - self.log.info("Validating: %s" % instance) - - -class MyExtractor(pyblish.api.InstancePlugin): - order = pyblish.api.ExtractorOrder - families = ["myFamily"] - label = "My Extractor" - - def process(self, instance): - self.log.info("Extracting: %s" % instance) - - -class CollectRenamed(pyblish.api.Collector): - def process(self, context): - i = context.create_instance("MyInstanceXYZ", family="MyFamily") - i.set_data("name", "My instance") - - -class CollectNegatron(pyblish.api.Collector): - """Negative collector adds Negatron""" - - order = pyblish.api.Collector.order - 0.49 - - def process_context(self, context): - self.log.info("Collecting Negatron") - context.create_instance("Negatron", family="MyFamily") - - -class CollectPositron(pyblish.api.Collector): - """Positive collector adds Positron""" - - order = pyblish.api.Collector.order + 0.49 - - def process_context(self, context): - self.log.info("Collecting Positron") - context.create_instance("Positron", family="MyFamily") - - -class SelectInstances(pyblish.api.Selector): - """Select debugging instances - - These instances are part of the evil plan to destroy the world. - Be weary, be vigilant, be sexy. - - """ - - def process_context(self, context): - self.log.info("Selecting instances..") - - for instance in instances[:-1]: - name, data = instance["name"], instance["data"] - self.log.info("Selecting: %s" % name) - instance = context.create_instance(name) - - for key, value in data.items(): - instance.set_data(key, value) - - -class SelectDiInstances(pyblish.api.Selector): - """Select DI instances""" - - name = "Select Dependency Instances" - - def process(self, context): - name, data = instances[-1]["name"], instances[-1]["data"] - self.log.info("Selecting: %s" % name) - instance = context.create_instance(name) - - for key, value in data.items(): - instance.set_data(key, value) - - -class SelectInstancesFailure(pyblish.api.Selector): - """Select some instances, but fail before adding anything to the context. - - That's right. I'm programmed to fail. Try me. - - """ - - __fail__ = True - - def process_context(self, context): - self.log.warning("I'm about to fail") - raise AssertionError("I was programmed to fail") - - -class SelectInstances2(pyblish.api.Selector): - def process(self, context): - self.log.warning("I'm good") - - -class ValidateNamespace(pyblish.api.Validator): - """Namespaces must be orange - - In case a namespace is not orange, report immediately to - your officer in charge, ask for a refund, do a backflip. - - This has been an example of: - - - A long doc-string - - With a list - - And plenty of newlines and tabs. - - """ - - families = ["B"] - - def process(self, instance): - self.log.info("Validating the namespace of %s" % instance.data("name")) - self.log.info("""And here's another message, quite long, in fact it's -too long to be displayed in a single row of text. -But that's how we roll down here. It's got \nnew lines\nas well. - -- And lists -- And more lists - - """) - - -class ValidateContext(pyblish.api.Validator): - families = ["A", "B"] - - def process_context(self, context): - self.log.info("Processing context..") - - -class ValidateContextFailure(pyblish.api.Validator): - optional = True - families = ["C"] - __fail__ = True - - def process_context(self, context): - self.log.info("About to fail..") - raise AssertionError("""I was programmed to fail - -The reason I failed was because the sun was not aligned with the tides, -and the moon is gray; not yellow. Try again when the moon is yellow.""") - - -class Validator1(pyblish.api.Validator): - """Test of the order attribute""" - order = pyblish.api.Validator.order + 0.1 - families = ["A"] - - def process_instance(self, instance): - pass - - -class Validator2(pyblish.api.Validator): - order = pyblish.api.Validator.order + 0.2 - families = ["B"] - - def process_instance(self, instance): - pass - - -class Validator3(pyblish.api.Validator): - order = pyblish.api.Validator.order + 0.3 - families = ["B"] - - def process_instance(self, instance): - pass - - -class ValidateFailureMock(pyblish.api.Validator): - """Plug-in that always fails""" - optional = True - order = pyblish.api.Validator.order + 0.1 - families = ["C"] - __fail__ = True - - def process_instance(self, instance): - self.log.debug("e = mc^2") - self.log.info("About to fail..") - self.log.warning("Failing.. soooon..") - self.log.critical("Ok, you're done.") - raise AssertionError("""ValidateFailureMock was destined to fail.. - -Here's some extended information about what went wrong. - -It has quite the long string associated with it, including -a few newlines and a list. - -- Item 1 -- Item 2 - -""") - - -class ValidateIsIncompatible(pyblish.api.Validator): - """This plug-in should never appear..""" - requires = False # This is invalid - - -class ValidateWithRepair(pyblish.api.Validator): - """A validator with repair functionality""" - optional = True - families = ["C"] - __fail__ = True - - def process_instance(self, instance): - raise AssertionError( - "%s is invalid, try repairing it!" % instance.name - ) - - def repair_instance(self, instance): - self.log.info("Attempting to repair..") - self.log.info("Success!") - - -class ValidateWithRepairFailure(pyblish.api.Validator): - """A validator with repair functionality that fails""" - optional = True - families = ["C"] - __fail__ = True - - def process_instance(self, instance): - raise AssertionError( - "%s is invalid, try repairing it!" % instance.name - ) - - def repair_instance(self, instance): - self.log.info("Attempting to repair..") - raise AssertionError("Could not repair due to X") - - -class ValidateWithVeryVeryVeryLongLongNaaaaame(pyblish.api.Validator): - """A validator with repair functionality that fails""" - families = ["A"] - - -class ValidateWithRepairContext(pyblish.api.Validator): - """A validator with repair functionality that fails""" - optional = True - families = ["C"] - __fail__ = True - - def process_context(self, context): - raise AssertionError("Could not validate context, try repairing it") - - def repair_context(self, context): - self.log.info("Attempting to repair..") - raise AssertionError("Could not repair") - - -class ExtractAsMa(pyblish.api.Extractor): - """Extract contents of each instance into .ma - - Serialise scene using Maya's own facilities and then put - it on the hard-disk. Once complete, this plug-in relies - on a Conformer to put it in it's final location, as this - extractor merely positions it in the users local temp- - directory. - - """ - - optional = True - __expected__ = { - "logCount": ">=4" - } - - def process_instance(self, instance): - self.log.info("About to extract scene to .ma..") - self.log.info("Extraction went well, now verifying the data..") - - if instance.name == "Richard05": - self.log.warning("You're almost running out of disk space!") - - self.log.info("About to finish up") - self.log.info("Finished successfully") - - -class ConformAsset(pyblish.api.Conformer): - """Conform the world - - Step 1: Conform all humans and Step 2: Conform all non-humans. - Once conforming has completed, rinse and repeat. - - """ - - optional = True - - def process_instance(self, instance): - self.log.info("About to conform all humans..") - - if instance.name == "Richard05": - self.log.warning("Richard05 is a conformist!") - - self.log.info("About to conform all non-humans..") - self.log.info("Conformed Successfully") - - -class ValidateInstancesDI(pyblish.api.Validator): - """Validate using the DI interface""" - families = ["diFamily"] - - def process(self, instance): - self.log.info("Validating %s.." % instance.data("name")) - - -class ValidateDIWithRepair(pyblish.api.Validator): - families = ["diFamily"] - optional = True - __fail__ = True - - def process(self, instance): - raise AssertionError("I was programmed to fail, for repair") - - def repair(self, instance): - self.log.info("Repairing %s" % instance.data("name")) - - -class ExtractInstancesDI(pyblish.api.Extractor): - """Extract using the DI interface""" - families = ["diFamily"] - - def process(self, instance): - self.log.info("Extracting %s.." % instance.data("name")) - - -class ValidateWithLabel(pyblish.api.Validator): - """Validate using the DI interface""" - label = "Validate with Label" - - -class ValidateWithLongLabel(pyblish.api.Validator): - """Validate using the DI interface""" - label = "Validate with Loooooooooooooooooooooong Label" - - -class SimplePlugin1(pyblish.api.Plugin): - """Validate using the simple-plugin interface""" - - def process(self): - self.log.info("I'm a simple plug-in, only processed once") - - -class SimplePlugin2(pyblish.api.Plugin): - """Validate using the simple-plugin interface - - It doesn't have an order, and will likely end up *before* all - other plug-ins. (due to how sorted([1, 2, 3, None]) works) - - """ - - def process(self, context): - self.log.info("Processing the context, simply: %s" % context) - - -class SimplePlugin3(pyblish.api.Plugin): - """Simply process every instance""" - - def process(self, instance): - self.log.info("Processing the instance, simply: %s" % instance) - - -class ContextAction(pyblish.api.Action): - label = "Context action" - - def process(self, context): - self.log.info("I have access to the context") - self.log.info("Context.instances: %s" % str(list(context))) - - -class FailingAction(pyblish.api.Action): - label = "Failing action" - - def process(self): - self.log.info("About to fail..") - raise Exception("I failed") - - -class LongRunningAction(pyblish.api.Action): - label = "Long-running action" - - def process(self): - self.log.info("Sleeping for 2 seconds..") - time.sleep(2) - self.log.info("Ah, that's better") - - -class IconAction(pyblish.api.Action): - label = "Icon action" - icon = "crop" - - def process(self): - self.log.info("I have an icon") - - -class PluginAction(pyblish.api.Action): - label = "Plugin action" - - def process(self, plugin): - self.log.info("I have access to my parent plug-in") - self.log.info("Which is %s" % plugin.id) - - -class LaunchExplorerAction(pyblish.api.Action): - label = "Open in Explorer" - icon = "folder-open" - - def process(self, context): - cwd = os.getcwd() - self.log.info("Opening %s in Explorer" % cwd) - result = subprocess.call("start .", cwd=cwd, shell=True) - self.log.debug(result) - - -class ProcessedAction(pyblish.api.Action): - label = "Success action" - icon = "check" - on = "processed" - - def process(self): - self.log.info("I am only available on a successful plug-in") - - -class FailedAction(pyblish.api.Action): - label = "Failure action" - icon = "close" - on = "failed" - - -class SucceededAction(pyblish.api.Action): - label = "Success action" - icon = "check" - on = "succeeded" - - def process(self): - self.log.info("I am only available on a successful plug-in") - - -class LongLabelAction(pyblish.api.Action): - label = "An incredibly, incredicly looooon label. Very long." - icon = "close" - - -class BadEventAction(pyblish.api.Action): - label = "Bad event action" - on = "not exist" - - -class InactiveAction(pyblish.api.Action): - active = False - - -class PluginWithActions(pyblish.api.Validator): - optional = True - actions = [ - pyblish.api.Category("General"), - ContextAction, - FailingAction, - LongRunningAction, - IconAction, - PluginAction, - pyblish.api.Category("Empty"), - pyblish.api.Category("OS"), - LaunchExplorerAction, - pyblish.api.Separator, - FailedAction, - SucceededAction, - pyblish.api.Category("Debug"), - BadEventAction, - InactiveAction, - LongLabelAction, - pyblish.api.Category("Empty"), - ] - - def process(self): - self.log.info("Ran PluginWithActions") - - -class FailingPluginWithActions(pyblish.api.Validator): - optional = True - actions = [ - FailedAction, - SucceededAction, - ] - - def process(self): - raise Exception("I was programmed to fail") - - -class ValidateDefaultOff(pyblish.api.Validator): - families = ["A", "B"] - active = False - optional = True - - def process(self, instance): - self.log.info("Processing instance..") - - -class ValidateWithHyperlinks(pyblish.api.Validator): - """To learn about Pyblish - - click here (http://pyblish.com) - - """ - - families = ["A", "B"] - - def process(self, instance): - self.log.info("Processing instance..") - - msg = "To learn about Pyblish, " - msg += "click here (http://pyblish.com)" - - self.log.info(msg) - - -class LongRunningCollector(pyblish.api.Collector): - """I will take at least 2 seconds...""" - def process(self, context): - self.log.info("Sleeping for 2 seconds..") - time.sleep(2) - self.log.info("Good morning") - - -class LongRunningValidator(pyblish.api.Validator): - """I will take at least 2 seconds...""" - def process(self, context): - self.log.info("Sleeping for 2 seconds..") - time.sleep(2) - self.log.info("Good morning") - - -class RearrangingPlugin(pyblish.api.ContextPlugin): - """Sort plug-ins by family, and then reverse it""" - order = pyblish.api.CollectorOrder + 0.2 - - def process(self, context): - self.log.info("Reversing instances in the context..") - context[:] = sorted( - context, - key=lambda i: i.data["family"], - reverse=True - ) - self.log.info("Reversed!") - - -class InactiveInstanceCollectorPlugin(pyblish.api.InstancePlugin): - """Special case of an InstancePlugin running as a Collector""" - order = pyblish.api.CollectorOrder + 0.1 - active = False - - def process(self, instance): - raise TypeError("I shouldn't have run in the first place") - - -class CollectWithIcon(pyblish.api.ContextPlugin): - order = pyblish.api.CollectorOrder - - def process(self, context): - instance = context.create_instance("With Icon") - instance.data["icon"] = "play" - - -instances = [ - { - "name": "Peter01", - "data": { - "family": "A", - "publish": False - } - }, - { - "name": "Richard05", - "data": { - "family": "A", - } - }, - { - "name": "Steven11", - "data": { - "family": "B", - } - }, - { - "name": "Piraya12", - "data": { - "family": "B", - } - }, - { - "name": "Marcus", - "data": { - "family": "C", - } - }, - { - "name": "Extra1", - "data": { - "family": "C", - } - }, - { - "name": "DependencyInstance", - "data": { - "family": "diFamily" - } - }, - { - "name": "NoFamily", - "data": {} - }, - { - "name": "Failure 1", - "data": { - "family": "failure", - "fail": False - } - }, - { - "name": "Failure 2", - "data": { - "family": "failure", - "fail": True - } - } -] - -plugins = [ - MyCollector, - MyValidator, - MyExtractor, - - CollectRenamed, - CollectNegatron, - CollectPositron, - SelectInstances, - SelectInstances2, - SelectDiInstances, - SelectInstancesFailure, - ValidateFailureMock, - ValidateNamespace, - # ValidateIsIncompatible, - ValidateWithVeryVeryVeryLongLongNaaaaame, - ValidateContext, - ValidateContextFailure, - Validator1, - Validator2, - Validator3, - ValidateWithRepair, - ValidateWithRepairFailure, - ValidateWithRepairContext, - ValidateWithLabel, - ValidateWithLongLabel, - ValidateDefaultOff, - ValidateWithHyperlinks, - ExtractAsMa, - ConformAsset, - - SimplePlugin1, - SimplePlugin2, - SimplePlugin3, - - ValidateInstancesDI, - ExtractInstancesDI, - ValidateDIWithRepair, - - PluginWithActions, - FailingPluginWithActions, - - # LongRunningCollector, - # LongRunningValidator, - - RearrangingPlugin, - InactiveInstanceCollectorPlugin, - - CollectComment, - CollectWithIcon, -] - -pyblish.api.sort_plugins(plugins) diff --git a/client/ayon_core/tools/pyblish_pype/model.py b/client/ayon_core/tools/pyblish_pype/model.py deleted file mode 100644 index 44f951fe14..0000000000 --- a/client/ayon_core/tools/pyblish_pype/model.py +++ /dev/null @@ -1,1116 +0,0 @@ -"""Qt models - -Description: - The model contains the original objects from Pyblish, such as - pyblish.api.Instance and pyblish.api.Plugin. The model then - provides an interface for reading and writing to those. - -GUI data: - Aside from original data, such as pyblish.api.Plugin.optional, - the GUI also hosts data internal to itself, such as whether or - not an item has processed such that it may be colored appropriately - in the view. This data is prefixed with two underscores (__). - - E.g. - - _has_processed - - This is so that the the GUI-only data doesn't accidentally overwrite - or cause confusion with existing data in plug-ins and instances. - -Roles: - Data is accessed via standard Qt "roles". You can think of a role - as the key of a dictionary, except they can only be integers. - -""" -from __future__ import unicode_literals - -import pyblish - -from . import settings, util -from .awesome import tags as awesome -from qtpy import QtCore, QtGui -import qtawesome -from .constants import PluginStates, InstanceStates, GroupStates, Roles - - -# ItemTypes -UserType = QtGui.QStandardItem.UserType -if hasattr(UserType, "value"): - UserType = UserType.value -InstanceType = UserType -PluginType = UserType + 1 -GroupType = UserType + 2 -TerminalLabelType = UserType + 3 -TerminalDetailType = UserType + 4 - - -class QAwesomeTextIconFactory: - icons = {} - - @classmethod - def icon(cls, icon_name): - if icon_name not in cls.icons: - cls.icons[icon_name] = awesome.get(icon_name) - return cls.icons[icon_name] - - -class QAwesomeIconFactory: - icons = {} - - @classmethod - def icon(cls, icon_name, icon_color): - if icon_name not in cls.icons: - cls.icons[icon_name] = {} - - if icon_color not in cls.icons[icon_name]: - cls.icons[icon_name][icon_color] = qtawesome.icon( - icon_name, - color=icon_color - ) - return cls.icons[icon_name][icon_color] - - -class IntentModel(QtGui.QStandardItemModel): - """Model for QComboBox with intents. - - It is expected that one inserted item is dictionary. - Key represents #Label and Value represent #Value. - - Example: - { - "Testing": "test", - "Publishing": "publish" - } - - First and default value is {"< Not Set >": None} - """ - - default_empty_label = "< Not set >" - - def __init__(self, parent=None): - super(IntentModel, self).__init__(parent) - self._item_count = 0 - self.default_index = 0 - - @property - def has_items(self): - return self._item_count > 0 - - def reset(self): - self.clear() - self._item_count = 0 - self.default_index = 0 - - # Intent settings are not available in core addon - intent_settings = {} - - items = intent_settings.get("items", {}) - if not items: - return - - allow_empty_intent = intent_settings.get("allow_empty_intent", True) - empty_intent_label = ( - intent_settings.get("empty_intent_label") - or self.default_empty_label - ) - listed_items = list(items.items()) - if allow_empty_intent: - listed_items.insert(0, ("", empty_intent_label)) - - default = intent_settings.get("default") - - for idx, item in enumerate(listed_items): - item_value = item[0] - if item_value == default: - self.default_index = idx - break - - self._add_items(listed_items) - - def _add_items(self, items): - for item in items: - value, label = item - new_item = QtGui.QStandardItem() - new_item.setData(label, QtCore.Qt.DisplayRole) - new_item.setData(value, Roles.IntentItemValue) - - self.setItem(self._item_count, new_item) - self._item_count += 1 - - -class PluginItem(QtGui.QStandardItem): - """Plugin item implementation.""" - - def __init__(self, plugin): - super(PluginItem, self).__init__() - - item_text = plugin.__name__ - if settings.UseLabel: - if hasattr(plugin, "label") and plugin.label: - item_text = plugin.label - - self.plugin = plugin - - self.setData(item_text, QtCore.Qt.DisplayRole) - self.setData(False, Roles.IsEnabledRole) - self.setData(0, Roles.PublishFlagsRole) - self.setData(0, Roles.PluginActionProgressRole) - icon_name = "" - if hasattr(plugin, "icon") and plugin.icon: - icon_name = plugin.icon - icon = QAwesomeTextIconFactory.icon(icon_name) - self.setData(icon, QtCore.Qt.DecorationRole) - - actions = [] - if hasattr(plugin, "actions") and plugin.actions: - actions = list(plugin.actions) - plugin.actions = actions - - is_checked = True - is_optional = getattr(plugin, "optional", False) - if is_optional: - is_checked = getattr(plugin, "active", True) - - plugin.active = is_checked - plugin.optional = is_optional - - self.setData( - "{}.{}".format(plugin.__module__, plugin.__name__), - Roles.ObjectUIdRole - ) - - self.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - - def type(self): - return PluginType - - def data(self, role=QtCore.Qt.DisplayRole): - if role == Roles.IsOptionalRole: - return self.plugin.optional - - if role == Roles.ObjectIdRole: - return self.plugin.id - - if role == Roles.TypeRole: - return self.type() - - if role == QtCore.Qt.CheckStateRole: - return self.plugin.active - - if role == Roles.PathModuleRole: - return self.plugin.__module__ - - if role == Roles.FamiliesRole: - return self.plugin.families - - if role == Roles.DocstringRole: - return self.plugin.__doc__ - - if role == Roles.PluginActionsVisibleRole: - return self._data_actions_visible() - - if role == Roles.PluginValidActionsRole: - return self._data_valid_actions() - - return super(PluginItem, self).data(role) - - def _data_actions_visible(self): - # Can only run actions on active plug-ins. - if not self.plugin.active or not self.plugin.actions: - return False - - publish_states = self.data(Roles.PublishFlagsRole) - if ( - not publish_states & PluginStates.IsCompatible - or publish_states & PluginStates.WasSkipped - ): - return False - - # Context specific actions - for action in self.plugin.actions: - if action.on == "failed": - if publish_states & PluginStates.HasError: - return True - - elif action.on == "succeeded": - if ( - publish_states & PluginStates.WasProcessed - and not publish_states & PluginStates.HasError - ): - return True - - elif action.on == "processed": - if publish_states & PluginStates.WasProcessed: - return True - - elif action.on == "notProcessed": - if not publish_states & PluginStates.WasProcessed: - return True - return False - - def _data_valid_actions(self): - valid_actions = [] - - # Can only run actions on active plug-ins. - if not self.plugin.active or not self.plugin.actions: - return valid_actions - - if not self.plugin.active or not self.plugin.actions: - return False - - publish_states = self.data(Roles.PublishFlagsRole) - if ( - not publish_states & PluginStates.IsCompatible - or publish_states & PluginStates.WasSkipped - ): - return False - - # Context specific actions - for action in self.plugin.actions: - valid = False - if action.on == "failed": - if publish_states & PluginStates.HasError: - valid = True - - elif action.on == "succeeded": - if ( - publish_states & PluginStates.WasProcessed - and not publish_states & PluginStates.HasError - ): - valid = True - - elif action.on == "processed": - if publish_states & PluginStates.WasProcessed: - valid = True - - elif action.on == "notProcessed": - if not publish_states & PluginStates.WasProcessed: - valid = True - - if valid: - valid_actions.append(action) - - if not valid_actions: - return valid_actions - - actions_len = len(valid_actions) - # Discard empty groups - indexex_to_remove = [] - for idx, action in enumerate(valid_actions): - if action.__type__ != "category": - continue - - next_id = idx + 1 - if next_id >= actions_len: - indexex_to_remove.append(idx) - continue - - next = valid_actions[next_id] - if next.__type__ != "action": - indexex_to_remove.append(idx) - - for idx in reversed(indexex_to_remove): - valid_actions.pop(idx) - - return valid_actions - - def setData(self, value, role=None): - if role is None: - role = QtCore.Qt.UserRole + 1 - - if role == QtCore.Qt.CheckStateRole: - if not self.data(Roles.IsEnabledRole): - return False - self.plugin.active = value - self.emitDataChanged() - return - - elif role == Roles.PluginActionProgressRole: - if isinstance(value, list): - _value = self.data(Roles.PluginActionProgressRole) - for flag in value: - _value |= flag - value = _value - - elif isinstance(value, dict): - _value = self.data(Roles.PluginActionProgressRole) - for flag, _bool in value.items(): - if _bool is True: - _value |= flag - elif _value & flag: - _value ^= flag - value = _value - - elif role == Roles.PublishFlagsRole: - if isinstance(value, list): - _value = self.data(Roles.PublishFlagsRole) - for flag in value: - _value |= flag - value = _value - - elif isinstance(value, dict): - _value = self.data(Roles.PublishFlagsRole) - for flag, _bool in value.items(): - if _bool is True: - _value |= flag - elif _value & flag: - _value ^= flag - value = _value - - if value & PluginStates.HasWarning: - if self.parent(): - self.parent().setData( - {GroupStates.HasWarning: True}, - Roles.PublishFlagsRole - ) - if value & PluginStates.HasError: - if self.parent(): - self.parent().setData( - {GroupStates.HasError: True}, - Roles.PublishFlagsRole - ) - - return super(PluginItem, self).setData(value, role) - - -class GroupItem(QtGui.QStandardItem): - def __init__(self, *args, **kwargs): - self.order = kwargs.pop("order", None) - self.publish_states = 0 - super(GroupItem, self).__init__(*args, **kwargs) - - def flags(self): - return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - - def data(self, role=QtCore.Qt.DisplayRole): - if role == Roles.PublishFlagsRole: - return self.publish_states - - if role == Roles.TypeRole: - return self.type() - - return super(GroupItem, self).data(role) - - def setData(self, value, role=(QtCore.Qt.UserRole + 1)): - if role == Roles.PublishFlagsRole: - if isinstance(value, list): - _value = self.data(Roles.PublishFlagsRole) - for flag in value: - _value |= flag - value = _value - - elif isinstance(value, dict): - _value = self.data(Roles.PublishFlagsRole) - for flag, _bool in value.items(): - if _bool is True: - _value |= flag - elif _value & flag: - _value ^= flag - value = _value - self.publish_states = value - self.emitDataChanged() - return True - - return super(GroupItem, self).setData(value, role) - - def type(self): - return GroupType - - -class PluginModel(QtGui.QStandardItemModel): - def __init__(self, controller, *args, **kwargs): - super(PluginModel, self).__init__(*args, **kwargs) - - self.controller = controller - self.checkstates = {} - self.group_items = {} - self.plugin_items = {} - - def reset(self): - self.group_items = {} - self.plugin_items = {} - self.clear() - - def append(self, plugin): - plugin_groups = self.controller.order_groups.groups - label = None - order = None - for _order, item in reversed(plugin_groups.items()): - if _order is None or plugin.order < _order: - label = item["label"] - order = _order - else: - break - - if label is None: - label = "Other" - - group_item = self.group_items.get(label) - if not group_item: - group_item = GroupItem(label, order=order) - self.appendRow(group_item) - self.group_items[label] = group_item - - new_item = PluginItem(plugin) - group_item.appendRow(new_item) - - self.plugin_items[plugin._id] = new_item - - def store_checkstates(self): - self.checkstates.clear() - - for plugin_item in self.plugin_items.values(): - if not plugin_item.plugin.optional: - continue - - uid = plugin_item.data(Roles.ObjectUIdRole) - self.checkstates[uid] = plugin_item.data(QtCore.Qt.CheckStateRole) - - def restore_checkstates(self): - for plugin_item in self.plugin_items.values(): - if not plugin_item.plugin.optional: - continue - - uid = plugin_item.data(Roles.ObjectUIdRole) - state = self.checkstates.get(uid) - if state is not None: - plugin_item.setData(state, QtCore.Qt.CheckStateRole) - - def update_with_result(self, result): - plugin = result["plugin"] - item = self.plugin_items[plugin.id] - - new_flag_states = { - PluginStates.InProgress: False, - PluginStates.WasProcessed: True - } - - publish_states = item.data(Roles.PublishFlagsRole) - - has_warning = publish_states & PluginStates.HasWarning - new_records = result.get("records") or [] - if not has_warning: - for record in new_records: - level_no = record.get("levelno") - if level_no and level_no >= 30: - new_flag_states[PluginStates.HasWarning] = True - break - - if ( - not publish_states & PluginStates.HasError - and not result["success"] - ): - new_flag_states[PluginStates.HasError] = True - - if not publish_states & PluginStates.IsCompatible: - new_flag_states[PluginStates.IsCompatible] = True - - item.setData(new_flag_states, Roles.PublishFlagsRole) - - records = item.data(Roles.LogRecordsRole) or [] - records.extend(new_records) - - item.setData(records, Roles.LogRecordsRole) - - return item - - def update_compatibility(self): - context = self.controller.context - - families = util.collect_families_from_instances(context, True) - for plugin_item in self.plugin_items.values(): - publish_states = plugin_item.data(Roles.PublishFlagsRole) - if ( - publish_states & PluginStates.WasProcessed - or publish_states & PluginStates.WasSkipped - ): - continue - - is_compatible = False - # A plugin should always show if it has processed. - if plugin_item.plugin.__instanceEnabled__: - compatible_instances = pyblish.logic.instances_by_plugin( - context, plugin_item.plugin - ) - for instance in context: - if not instance.data.get("publish"): - continue - - if instance in compatible_instances: - is_compatible = True - break - else: - plugins = pyblish.logic.plugins_by_families( - [plugin_item.plugin], families - ) - if plugins: - is_compatible = True - - current_is_compatible = publish_states & PluginStates.IsCompatible - if ( - (is_compatible and not current_is_compatible) - or (not is_compatible and current_is_compatible) - ): - new_flag = { - PluginStates.IsCompatible: is_compatible - } - plugin_item.setData(new_flag, Roles.PublishFlagsRole) - - -class PluginFilterProxy(QtCore.QSortFilterProxyModel): - def filterAcceptsRow(self, source_row, source_parent): - index = self.sourceModel().index(source_row, 0, source_parent) - item_type = index.data(Roles.TypeRole) - if item_type != PluginType: - return True - - publish_states = index.data(Roles.PublishFlagsRole) - if ( - publish_states & PluginStates.WasSkipped - or not publish_states & PluginStates.IsCompatible - ): - return False - return True - - -class InstanceItem(QtGui.QStandardItem): - """Instance item implementation.""" - - def __init__(self, instance): - super(InstanceItem, self).__init__() - - self.instance = instance - self.is_context = False - publish_states = getattr(instance, "_publish_states", 0) - if publish_states & InstanceStates.ContextType: - self.is_context = True - - instance._publish_states = publish_states - instance._logs = [] - instance.optional = getattr(instance, "optional", True) - instance.data["publish"] = instance.data.get("publish", True) - - family = self.data(Roles.FamiliesRole)[0] - self.setData( - "{}.{}".format(family, self.instance.data["name"]), - Roles.ObjectUIdRole - ) - - def flags(self): - return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - - def type(self): - return InstanceType - - def data(self, role=QtCore.Qt.DisplayRole): - if role == QtCore.Qt.DisplayRole: - label = None - if settings.UseLabel: - label = self.instance.data.get("label") - - if not label: - if self.is_context: - label = "Context" - else: - label = self.instance.data["name"] - return label - - if role == QtCore.Qt.DecorationRole: - icon_name = self.instance.data.get("icon") or "file" - return QAwesomeTextIconFactory.icon(icon_name) - - if role == Roles.TypeRole: - return self.type() - - if role == Roles.ObjectIdRole: - return self.instance.id - - if role == Roles.FamiliesRole: - if self.is_context: - return ["Context"] - - families = [] - family = self.instance.data.get("family") - if family: - families.append(family) - - _families = self.instance.data.get("families") or [] - for _family in _families: - if _family not in families: - families.append(_family) - - return families - - if role == Roles.IsOptionalRole: - return self.instance.optional - - if role == QtCore.Qt.CheckStateRole: - return self.instance.data["publish"] - - if role == Roles.PublishFlagsRole: - return self.instance._publish_states - - if role == Roles.LogRecordsRole: - return self.instance._logs - - return super(InstanceItem, self).data(role) - - def setData(self, value, role=(QtCore.Qt.UserRole + 1)): - if role == QtCore.Qt.CheckStateRole: - if not self.data(Roles.IsEnabledRole): - return - self.instance.data["publish"] = value - self.emitDataChanged() - return - - if role == Roles.IsEnabledRole: - if not self.instance.optional: - return - - if role == Roles.PublishFlagsRole: - if isinstance(value, list): - _value = self.instance._publish_states - for flag in value: - _value |= flag - value = _value - - elif isinstance(value, dict): - _value = self.instance._publish_states - for flag, _bool in value.items(): - if _bool is True: - _value |= flag - elif _value & flag: - _value ^= flag - value = _value - - if value & InstanceStates.HasWarning: - if self.parent(): - self.parent().setData( - {GroupStates.HasWarning: True}, - Roles.PublishFlagsRole - ) - if value & InstanceStates.HasError: - if self.parent(): - self.parent().setData( - {GroupStates.HasError: True}, - Roles.PublishFlagsRole - ) - - self.instance._publish_states = value - self.emitDataChanged() - return - - if role == Roles.LogRecordsRole: - self.instance._logs = value - self.emitDataChanged() - return - - return super(InstanceItem, self).setData(value, role) - - -class InstanceModel(QtGui.QStandardItemModel): - - group_created = QtCore.Signal(QtCore.QModelIndex) - - def __init__(self, controller, *args, **kwargs): - super(InstanceModel, self).__init__(*args, **kwargs) - - self.controller = controller - self.checkstates = {} - self.group_items = {} - self.instance_items = {} - - def reset(self): - self.group_items = {} - self.instance_items = {} - self.clear() - - def append(self, instance): - new_item = InstanceItem(instance) - if new_item.is_context: - self.appendRow(new_item) - else: - families = new_item.data(Roles.FamiliesRole) - group_item = self.group_items.get(families[0]) - if not group_item: - group_item = GroupItem(families[0]) - self.appendRow(group_item) - self.group_items[families[0]] = group_item - self.group_created.emit(group_item.index()) - - group_item.appendRow(new_item) - instance_id = instance.id - self.instance_items[instance_id] = new_item - - def remove(self, instance_id): - instance_item = self.instance_items.pop(instance_id) - parent_item = instance_item.parent() - parent_item.removeRow(instance_item.row()) - if parent_item.rowCount(): - return - - self.group_items.pop(parent_item.data(QtCore.Qt.DisplayRole)) - self.removeRow(parent_item.row()) - - def store_checkstates(self): - self.checkstates.clear() - - for instance_item in self.instance_items.values(): - if not instance_item.instance.optional: - continue - - uid = instance_item.data(Roles.ObjectUIdRole) - self.checkstates[uid] = instance_item.data( - QtCore.Qt.CheckStateRole - ) - - def restore_checkstates(self): - for instance_item in self.instance_items.values(): - if not instance_item.instance.optional: - continue - - uid = instance_item.data(Roles.ObjectUIdRole) - state = self.checkstates.get(uid) - if state is not None: - instance_item.setData(state, QtCore.Qt.CheckStateRole) - - def update_with_result(self, result): - instance = result["instance"] - if instance is None: - instance_id = self.controller.context.id - else: - instance_id = instance.id - - item = self.instance_items.get(instance_id) - if not item: - return - - new_flag_states = { - InstanceStates.InProgress: False - } - - publish_states = item.data(Roles.PublishFlagsRole) - has_warning = publish_states & InstanceStates.HasWarning - new_records = result.get("records") or [] - if not has_warning: - for record in new_records: - level_no = record.get("levelno") - if level_no and level_no >= 30: - new_flag_states[InstanceStates.HasWarning] = True - break - - if ( - not publish_states & InstanceStates.HasError - and not result["success"] - ): - new_flag_states[InstanceStates.HasError] = True - - item.setData(new_flag_states, Roles.PublishFlagsRole) - - records = item.data(Roles.LogRecordsRole) or [] - records.extend(new_records) - - item.setData(records, Roles.LogRecordsRole) - - return item - - def update_compatibility(self, context, instances): - families = util.collect_families_from_instances(context, True) - for plugin_item in self.plugin_items.values(): - publish_states = plugin_item.data(Roles.PublishFlagsRole) - if ( - publish_states & PluginStates.WasProcessed - or publish_states & PluginStates.WasSkipped - ): - continue - - is_compatible = False - # A plugin should always show if it has processed. - if plugin_item.plugin.__instanceEnabled__: - compatibleInstances = pyblish.logic.instances_by_plugin( - context, plugin_item.plugin - ) - for instance in instances: - if not instance.data.get("publish"): - continue - - if instance in compatibleInstances: - is_compatible = True - break - else: - plugins = pyblish.logic.plugins_by_families( - [plugin_item.plugin], families - ) - if plugins: - is_compatible = True - - current_is_compatible = publish_states & PluginStates.IsCompatible - if ( - (is_compatible and not current_is_compatible) - or (not is_compatible and current_is_compatible) - ): - plugin_item.setData( - {PluginStates.IsCompatible: is_compatible}, - Roles.PublishFlagsRole - ) - - -class InstanceSortProxy(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(InstanceSortProxy, self).__init__(*args, **kwargs) - # Do not care about lower/upper case - self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - - def lessThan(self, x_index, y_index): - x_type = x_index.data(Roles.TypeRole) - y_type = y_index.data(Roles.TypeRole) - if x_type != y_type: - if x_type == GroupType: - return False - return True - return super(InstanceSortProxy, self).lessThan(x_index, y_index) - - -class TerminalDetailItem(QtGui.QStandardItem): - key_label_record_map = ( - ("instance", "Instance"), - ("msg", "Message"), - ("name", "Plugin"), - ("pathname", "Path"), - ("lineno", "Line"), - ("traceback", "Traceback"), - ("levelname", "Level"), - ("threadName", "Thread"), - ("msecs", "Millis") - ) - - def __init__(self, record_item): - self.record_item = record_item - self.msg = None - msg = record_item.get("msg") - if msg is None: - msg = record_item["label"].split("\n")[0] - - super(TerminalDetailItem, self).__init__(msg) - - def data(self, role=QtCore.Qt.DisplayRole): - if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): - if self.msg is None: - self.msg = self.compute_detail_text(self.record_item) - return self.msg - return super(TerminalDetailItem, self).data(role) - - def compute_detail_text(self, item_data): - if item_data["type"] == "info": - return item_data["label"] - - html_text = "" - for key, title in self.key_label_record_map: - if key not in item_data: - continue - value = item_data[key] - text = ( - str(value) - .replace("<", "<") - .replace(">", ">") - .replace('\n', '
') - .replace(' ', ' ') - ) - - title_tag = ( - '{}: ' - ' color:#fff;\" >{}: ' - ).format(title) - - html_text += ( - '{}' - '{}' - ).format(title_tag, text) - - html_text = '{}
'.format( - html_text - ) - return html_text - - -class TerminalModel(QtGui.QStandardItemModel): - item_icon_name = { - "info": "fa.info", - "record": "fa.circle", - "error": "fa.exclamation-triangle", - } - - item_icon_colors = { - "info": "#ffffff", - "error": "#ff4a4a", - "log_debug": "#ff66e8", - "log_info": "#66abff", - "log_warning": "#ffba66", - "log_error": "#ff4d58", - "log_critical": "#ff4f75", - None: "#333333" - } - - level_to_record = ( - (10, "log_debug"), - (20, "log_info"), - (30, "log_warning"), - (40, "log_error"), - (50, "log_critical") - - ) - - def __init__(self, *args, **kwargs): - super(TerminalModel, self).__init__(*args, **kwargs) - self.reset() - - def reset(self): - self.clear() - - def prepare_records(self, result, suspend_logs): - prepared_records = [] - instance_name = None - instance = result["instance"] - if instance is not None: - instance_name = instance.data["name"] - - if not suspend_logs: - for record in result.get("records") or []: - if isinstance(record, dict): - record_item = record - else: - record_item = { - "label": str(record.msg), - "type": "record", - "levelno": record.levelno, - "threadName": record.threadName, - "name": record.name, - "filename": record.filename, - "pathname": record.pathname, - "lineno": record.lineno, - "msg": str(record.msg), - "msecs": record.msecs, - "levelname": record.levelname - } - - if instance_name is not None: - record_item["instance"] = instance_name - - prepared_records.append(record_item) - - error = result.get("error") - if error: - fname, line_no, func, exc = error.traceback - error_item = { - "label": str(error), - "type": "error", - "filename": str(fname), - "lineno": str(line_no), - "func": str(func), - "traceback": error.formatted_traceback, - } - - if instance_name is not None: - error_item["instance"] = instance_name - - prepared_records.append(error_item) - - return prepared_records - - def append(self, record_items): - all_record_items = [] - for record_item in record_items: - record_type = record_item["type"] - # Add error message to detail - if record_type == "error": - record_item["msg"] = record_item["label"] - terminal_item_type = None - if record_type == "record": - for level, _type in self.level_to_record: - if level > record_item["levelno"]: - break - terminal_item_type = _type - - else: - terminal_item_type = record_type - - icon_color = self.item_icon_colors.get(terminal_item_type) - icon_name = self.item_icon_name.get(record_type) - - top_item_icon = None - if icon_color and icon_name: - top_item_icon = QAwesomeIconFactory.icon(icon_name, icon_color) - - label = record_item["label"].split("\n")[0] - - top_item = QtGui.QStandardItem() - all_record_items.append(top_item) - - detail_item = TerminalDetailItem(record_item) - top_item.appendRow(detail_item) - - top_item.setData(TerminalLabelType, Roles.TypeRole) - top_item.setData(terminal_item_type, Roles.TerminalItemTypeRole) - top_item.setData(label, QtCore.Qt.DisplayRole) - top_item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - - if top_item_icon: - top_item.setData(top_item_icon, QtCore.Qt.DecorationRole) - - detail_item.setData(TerminalDetailType, Roles.TypeRole) - - self.invisibleRootItem().appendRows(all_record_items) - - def update_with_result(self, result): - self.append(result["records"]) - - -class TerminalProxy(QtCore.QSortFilterProxyModel): - filter_buttons_checks = { - "info": settings.TerminalFilters.get("info", True), - "log_debug": settings.TerminalFilters.get("log_debug", True), - "log_info": settings.TerminalFilters.get("log_info", True), - "log_warning": settings.TerminalFilters.get("log_warning", True), - "log_error": settings.TerminalFilters.get("log_error", True), - "log_critical": settings.TerminalFilters.get("log_critical", True), - "error": settings.TerminalFilters.get("error", True) - } - - instances = [] - - def __init__(self, view, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - self.__class__.instances.append(self) - # Store parent because by own `QSortFilterProxyModel` has `parent` - # method not returning parent QObject in PySide and PyQt4 - self.view = view - - @classmethod - def change_filter(cls, name, value): - cls.filter_buttons_checks[name] = value - - for instance in cls.instances: - try: - instance.invalidate() - if instance.view: - instance.view.updateGeometry() - - except RuntimeError: - # C++ Object was deleted - cls.instances.remove(instance) - - def filterAcceptsRow(self, source_row, source_parent): - index = self.sourceModel().index(source_row, 0, source_parent) - item_type = index.data(Roles.TypeRole) - if not item_type == TerminalLabelType: - return True - terminal_item_type = index.data(Roles.TerminalItemTypeRole) - return self.__class__.filter_buttons_checks.get( - terminal_item_type, True - ) diff --git a/client/ayon_core/tools/pyblish_pype/settings.py b/client/ayon_core/tools/pyblish_pype/settings.py deleted file mode 100644 index 5b69eb6a50..0000000000 --- a/client/ayon_core/tools/pyblish_pype/settings.py +++ /dev/null @@ -1,30 +0,0 @@ -from .util import env_variable_to_bool - -# Customize the window of the pyblish-lite window. -WindowTitle = "Pyblish" - -# Customize whether to show label names for plugins. -UseLabel = True - -# Customize which tab to start on. Possible choices are: "artist", "overview" -# and "terminal". -InitialTab = "overview" - -# Customize the window size. -WindowSize = (430, 600) - -TerminalFilters = { - "info": True, - "log_debug": True, - "log_info": True, - "log_warning": True, - "log_error": True, - "log_critical": True, - "traceback": True, -} - -# Allow animations in GUI -Animated = env_variable_to_bool("AYON_PYBLISH_ANIMATED", True) - -# Print UI info message to console -PrintInfo = env_variable_to_bool("AYON_PYBLISH_PRINT_INFO", True) diff --git a/client/ayon_core/tools/pyblish_pype/util.py b/client/ayon_core/tools/pyblish_pype/util.py deleted file mode 100644 index 081f7775d5..0000000000 --- a/client/ayon_core/tools/pyblish_pype/util.py +++ /dev/null @@ -1,144 +0,0 @@ -from __future__ import ( - absolute_import, - division, - print_function, - unicode_literals -) - -import os -import sys -import collections - -from qtpy import QtCore -import pyblish.api - -root = os.path.dirname(__file__) - - -def get_asset(*path): - """Return path to asset, relative the install directory - - Usage: - >>> path = get_asset("dir", "to", "asset.png") - >>> path == os.path.join(root, "dir", "to", "asset.png") - True - - Arguments: - path (str): One or more paths, to be concatenated - - """ - - return os.path.join(root, *path) - - -def defer(delay, func): - """Append artificial delay to `func` - - This aids in keeping the GUI responsive, but complicates logic - when producing tests. To combat this, the environment variable ensures - that every operation is synchronous. - - Arguments: - delay (float): Delay multiplier; default 1, 0 means no delay - func (callable): Any callable - - """ - - delay *= float(os.getenv("PYBLISH_DELAY", 1)) - if delay > 0: - return QtCore.QTimer.singleShot(delay, func) - else: - return func() - - -def u_print(msg, **kwargs): - """`print` with encoded unicode. - - `print` unicode may cause UnicodeEncodeError - or non-readable result when `PYTHONIOENCODING` is not set. - this will fix it. - - Arguments: - msg (unicode): Message to print. - **kwargs: Keyword argument for `print` function. - """ - - if isinstance(msg, str): - encoding = None - try: - encoding = os.getenv('PYTHONIOENCODING', sys.stdout.encoding) - except AttributeError: - # `sys.stdout.encoding` may not exists. - pass - msg = msg.encode(encoding or 'utf-8', 'replace') - print(msg, **kwargs) - - -def collect_families_from_instances(instances, only_active=False): - all_families = set() - for instance in instances: - if only_active: - if instance.data.get("publish") is False: - continue - family = instance.data.get("family") - if family: - all_families.add(family) - - families = instance.data.get("families") or tuple() - for family in families: - all_families.add(family) - - return list(all_families) - - -class OrderGroups: - validation_order = pyblish.api.ValidatorOrder + 0.5 - groups = collections.OrderedDict(( - ( - pyblish.api.CollectorOrder + 0.5, - { - "label": "Collect", - "state": "Collecting.." - } - ), - ( - pyblish.api.ValidatorOrder + 0.5, - { - "label": "Validate", - "state": "Validating.." - } - ), - ( - pyblish.api.ExtractorOrder + 0.5, - { - "label": "Extract", - "state": "Extracting.." - } - ), - ( - pyblish.api.IntegratorOrder + 0.5, - { - "label": "Integrate", - "state": "Integrating.." - } - ), - ( - None, - { - "label": "Other", - "state": "Finishing.." - } - ) - )) - - -def env_variable_to_bool(env_key, default=False): - """Boolean based on environment variable value.""" - value = os.environ.get(env_key) - if value is not None: - value = value.lower() - if value in ("true", "1", "yes", "on"): - return True - elif value in ("false", "0", "no", "off"): - return False - return default diff --git a/client/ayon_core/tools/pyblish_pype/vendor/__init__.py b/client/ayon_core/tools/pyblish_pype/vendor/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/__init__.py b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/__init__.py deleted file mode 100644 index 4a0001ebb7..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -qtawesome - use font-awesome in PyQt / PySide applications - -This is a port to Python of the C++ QtAwesome library by Rick Blommers -""" -from .iconic_font import IconicFont, set_global_defaults -from .animation import Pulse, Spin -from ._version import version_info, __version__ - -_resource = {'iconic': None, } - - -def _instance(): - if _resource['iconic'] is None: - _resource['iconic'] = IconicFont(('fa', 'fontawesome-webfont.ttf', 'fontawesome-webfont-charmap.json'), - ('ei', 'elusiveicons-webfont.ttf', 'elusiveicons-webfont-charmap.json')) - return _resource['iconic'] - - -def icon(*args, **kwargs): - return _instance().icon(*args, **kwargs) - - -def load_font(*args, **kwargs): - return _instance().load_font(*args, **kwargs) - - -def charmap(prefixed_name): - prefix, name = prefixed_name.split('.') - return _instance().charmap[prefix][name] - - -def font(*args, **kwargs): - return _instance().font(*args, **kwargs) - - -def set_defaults(**kwargs): - return set_global_defaults(**kwargs) - diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/_version.py b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/_version.py deleted file mode 100644 index 7af886d1a0..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -version_info = (0, 3, 0, 'dev') -__version__ = '.'.join(map(str, version_info)) diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/animation.py b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/animation.py deleted file mode 100644 index ac69507444..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/animation.py +++ /dev/null @@ -1,41 +0,0 @@ -from qtpy import QtCore - - -class Spin: - - def __init__(self, parent_widget, interval=10, step=1): - self.parent_widget = parent_widget - self.interval, self.step = interval, step - self.info = {} - - def _update(self, parent_widget): - if self.parent_widget in self.info: - timer, angle, step = self.info[self.parent_widget] - - if angle >= 360: - angle = 0 - - angle += step - self.info[parent_widget] = timer, angle, step - parent_widget.update() - - def setup(self, icon_painter, painter, rect): - - if self.parent_widget not in self.info: - timer = QtCore.QTimer() - timer.timeout.connect(lambda: self._update(self.parent_widget)) - self.info[self.parent_widget] = [timer, 0, self.step] - timer.start(self.interval) - else: - timer, angle, self.step = self.info[self.parent_widget] - x_center = rect.width() * 0.5 - y_center = rect.height() * 0.5 - painter.translate(x_center, y_center) - painter.rotate(angle) - painter.translate(-x_center, -y_center) - - -class Pulse(Spin): - - def __init__(self, parent_widget): - Spin.__init__(self, parent_widget, interval=300, step=45) diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json deleted file mode 100644 index 099bcb818c..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json +++ /dev/null @@ -1,306 +0,0 @@ -{ - "address-book": "0xf102", - "address-book-alt": "0xf101", - "adjust": "0xf104", - "adjust-alt": "0xf103", - "adult": "0xf105", - "align-center": "0xf106", - "align-justify": "0xf107", - "align-left": "0xf108", - "align-right": "0xf109", - "arrow-down": "0xf10a", - "arrow-left": "0xf10b", - "arrow-right": "0xf10c", - "arrow-up": "0xf10d", - "asl": "0xf10e", - "asterisk": "0xf10f", - "backward": "0xf110", - "ban-circle": "0xf111", - "barcode": "0xf112", - "behance": "0xf113", - "bell": "0xf114", - "blind": "0xf115", - "blogger": "0xf116", - "bold": "0xf117", - "book": "0xf118", - "bookmark": "0xf11a", - "bookmark-empty": "0xf119", - "braille": "0xf11b", - "briefcase": "0xf11c", - "broom": "0xf11d", - "brush": "0xf11e", - "bulb": "0xf11f", - "bullhorn": "0xf120", - "calendar": "0xf122", - "calendar-sign": "0xf121", - "camera": "0xf123", - "car": "0xf124", - "caret-down": "0xf125", - "caret-left": "0xf126", - "caret-right": "0xf127", - "caret-up": "0xf128", - "cc": "0xf129", - "certificate": "0xf12a", - "check": "0xf12c", - "check-empty": "0xf12b", - "chevron-down": "0xf12d", - "chevron-left": "0xf12e", - "chevron-right": "0xf12f", - "chevron-up": "0xf130", - "child": "0xf131", - "circle-arrow-down": "0xf132", - "circle-arrow-left": "0xf133", - "circle-arrow-right": "0xf134", - "circle-arrow-up": "0xf135", - "cloud": "0xf137", - "cloud-alt": "0xf136", - "cog": "0xf139", - "cog-alt": "0xf138", - "cogs": "0xf13a", - "comment": "0xf13c", - "comment-alt": "0xf13b", - "compass": "0xf13e", - "compass-alt": "0xf13d", - "credit-card": "0xf13f", - "css": "0xf140", - "dashboard": "0xf141", - "delicious": "0xf142", - "deviantart": "0xf143", - "digg": "0xf144", - "download": "0xf146", - "download-alt": "0xf145", - "dribbble": "0xf147", - "edit": "0xf148", - "eject": "0xf149", - "envelope": "0xf14b", - "envelope-alt": "0xf14a", - "error": "0xf14d", - "error-alt": "0xf14c", - "eur": "0xf14e", - "exclamation-sign": "0xf14f", - "eye-close": "0xf150", - "eye-open": "0xf151", - "facebook": "0xf152", - "facetime-video": "0xf153", - "fast-backward": "0xf154", - "fast-forward": "0xf155", - "female": "0xf156", - "file": "0xf15c", - "file-alt": "0xf157", - "file-edit": "0xf159", - "file-edit-alt": "0xf158", - "file-new": "0xf15b", - "file-new-alt": "0xf15a", - "film": "0xf15d", - "filter": "0xf15e", - "fire": "0xf15f", - "flag": "0xf161", - "flag-alt": "0xf160", - "flickr": "0xf162", - "folder": "0xf166", - "folder-close": "0xf163", - "folder-open": "0xf164", - "folder-sign": "0xf165", - "font": "0xf167", - "fontsize": "0xf168", - "fork": "0xf169", - "forward": "0xf16b", - "forward-alt": "0xf16a", - "foursquare": "0xf16c", - "friendfeed": "0xf16e", - "friendfeed-rect": "0xf16d", - "fullscreen": "0xf16f", - "gbp": "0xf170", - "gift": "0xf171", - "github": "0xf173", - "github-text": "0xf172", - "glass": "0xf174", - "glasses": "0xf175", - "globe": "0xf177", - "globe-alt": "0xf176", - "googleplus": "0xf178", - "graph": "0xf17a", - "graph-alt": "0xf179", - "group": "0xf17c", - "group-alt": "0xf17b", - "guidedog": "0xf17d", - "hand-down": "0xf17e", - "hand-left": "0xf17f", - "hand-right": "0xf180", - "hand-up": "0xf181", - "hdd": "0xf182", - "headphones": "0xf183", - "hearing-impaired": "0xf184", - "heart": "0xf187", - "heart-alt": "0xf185", - "heart-empty": "0xf186", - "home": "0xf189", - "home-alt": "0xf188", - "hourglass": "0xf18a", - "idea": "0xf18c", - "idea-alt": "0xf18b", - "inbox": "0xf18f", - "inbox-alt": "0xf18d", - "inbox-box": "0xf18e", - "indent-left": "0xf190", - "indent-right": "0xf191", - "info-circle": "0xf192", - "instagram": "0xf193", - "iphone-home": "0xf194", - "italic": "0xf195", - "key": "0xf196", - "laptop": "0xf198", - "laptop-alt": "0xf197", - "lastfm": "0xf199", - "leaf": "0xf19a", - "lines": "0xf19b", - "link": "0xf19c", - "linkedin": "0xf19d", - "list": "0xf19f", - "list-alt": "0xf19e", - "livejournal": "0xf1a0", - "lock": "0xf1a2", - "lock-alt": "0xf1a1", - "magic": "0xf1a3", - "magnet": "0xf1a4", - "male": "0xf1a5", - "map-marker": "0xf1a7", - "map-marker-alt": "0xf1a6", - "mic": "0xf1a9", - "mic-alt": "0xf1a8", - "minus": "0xf1ab", - "minus-sign": "0xf1aa", - "move": "0xf1ac", - "music": "0xf1ad", - "myspace": "0xf1ae", - "network": "0xf1af", - "off": "0xf1b0", - "ok": "0xf1b3", - "ok-circle": "0xf1b1", - "ok-sign": "0xf1b2", - "opensource": "0xf1b4", - "paper-clip": "0xf1b6", - "paper-clip-alt": "0xf1b5", - "path": "0xf1b7", - "pause": "0xf1b9", - "pause-alt": "0xf1b8", - "pencil": "0xf1bb", - "pencil-alt": "0xf1ba", - "person": "0xf1bc", - "phone": "0xf1be", - "phone-alt": "0xf1bd", - "photo": "0xf1c0", - "photo-alt": "0xf1bf", - "picasa": "0xf1c1", - "picture": "0xf1c2", - "pinterest": "0xf1c3", - "plane": "0xf1c4", - "play": "0xf1c7", - "play-alt": "0xf1c5", - "play-circle": "0xf1c6", - "plurk": "0xf1c9", - "plurk-alt": "0xf1c8", - "plus": "0xf1cb", - "plus-sign": "0xf1ca", - "podcast": "0xf1cc", - "print": "0xf1cd", - "puzzle": "0xf1ce", - "qrcode": "0xf1cf", - "question": "0xf1d1", - "question-sign": "0xf1d0", - "quote-alt": "0xf1d2", - "quote-right": "0xf1d4", - "quote-right-alt": "0xf1d3", - "quotes": "0xf1d5", - "random": "0xf1d6", - "record": "0xf1d7", - "reddit": "0xf1d8", - "redux": "0xf1d9", - "refresh": "0xf1da", - "remove": "0xf1dd", - "remove-circle": "0xf1db", - "remove-sign": "0xf1dc", - "repeat": "0xf1df", - "repeat-alt": "0xf1de", - "resize-full": "0xf1e0", - "resize-horizontal": "0xf1e1", - "resize-small": "0xf1e2", - "resize-vertical": "0xf1e3", - "return-key": "0xf1e4", - "retweet": "0xf1e5", - "reverse-alt": "0xf1e6", - "road": "0xf1e7", - "rss": "0xf1e8", - "scissors": "0xf1e9", - "screen": "0xf1eb", - "screen-alt": "0xf1ea", - "screenshot": "0xf1ec", - "search": "0xf1ee", - "search-alt": "0xf1ed", - "share": "0xf1f0", - "share-alt": "0xf1ef", - "shopping-cart": "0xf1f2", - "shopping-cart-sign": "0xf1f1", - "signal": "0xf1f3", - "skype": "0xf1f4", - "slideshare": "0xf1f5", - "smiley": "0xf1f7", - "smiley-alt": "0xf1f6", - "soundcloud": "0xf1f8", - "speaker": "0xf1f9", - "spotify": "0xf1fa", - "stackoverflow": "0xf1fb", - "star": "0xf1fe", - "star-alt": "0xf1fc", - "star-empty": "0xf1fd", - "step-backward": "0xf1ff", - "step-forward": "0xf200", - "stop": "0xf202", - "stop-alt": "0xf201", - "stumbleupon": "0xf203", - "tag": "0xf204", - "tags": "0xf205", - "tasks": "0xf206", - "text-height": "0xf207", - "text-width": "0xf208", - "th": "0xf20b", - "th-large": "0xf209", - "th-list": "0xf20a", - "thumbs-down": "0xf20c", - "thumbs-up": "0xf20d", - "time": "0xf20f", - "time-alt": "0xf20e", - "tint": "0xf210", - "torso": "0xf211", - "trash": "0xf213", - "trash-alt": "0xf212", - "tumblr": "0xf214", - "twitter": "0xf215", - "universal-access": "0xf216", - "unlock": "0xf218", - "unlock-alt": "0xf217", - "upload": "0xf219", - "usd": "0xf21a", - "user": "0xf21b", - "viadeo": "0xf21c", - "video": "0xf21f", - "video-alt": "0xf21d", - "video-chat": "0xf21e", - "view-mode": "0xf220", - "vimeo": "0xf221", - "vkontakte": "0xf222", - "volume-down": "0xf223", - "volume-off": "0xf224", - "volume-up": "0xf225", - "w3c": "0xf226", - "warning-sign": "0xf227", - "website": "0xf229", - "website-alt": "0xf228", - "wheelchair": "0xf22a", - "wordpress": "0xf22b", - "wrench": "0xf22d", - "wrench-alt": "0xf22c", - "youtube": "0xf22e", - "zoom-in": "0xf22f", - "zoom-out": "0xf230" -} diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf deleted file mode 100644 index b6fe85d4b2..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json deleted file mode 100644 index 0e97d031e6..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json +++ /dev/null @@ -1,696 +0,0 @@ -{ - "500px": "f26e", - "adjust": "f042", - "adn": "f170", - "align-center": "f037", - "align-justify": "f039", - "align-left": "f036", - "align-right": "f038", - "amazon": "f270", - "ambulance": "f0f9", - "anchor": "f13d", - "android": "f17b", - "angellist": "f209", - "angle-double-down": "f103", - "angle-double-left": "f100", - "angle-double-right": "f101", - "angle-double-up": "f102", - "angle-down": "f107", - "angle-left": "f104", - "angle-right": "f105", - "angle-up": "f106", - "apple": "f179", - "archive": "f187", - "area-chart": "f1fe", - "arrow-circle-down": "f0ab", - "arrow-circle-left": "f0a8", - "arrow-circle-o-down": "f01a", - "arrow-circle-o-left": "f190", - "arrow-circle-o-right": "f18e", - "arrow-circle-o-up": "f01b", - "arrow-circle-right": "f0a9", - "arrow-circle-up": "f0aa", - "arrow-down": "f063", - "arrow-left": "f060", - "arrow-right": "f061", - "arrow-up": "f062", - "arrows": "f047", - "arrows-alt": "f0b2", - "arrows-h": "f07e", - "arrows-v": "f07d", - "asterisk": "f069", - "at": "f1fa", - "automobile": "f1b9", - "backward": "f04a", - "balance-scale": "f24e", - "ban": "f05e", - "bank": "f19c", - "bar-chart": "f080", - "bar-chart-o": "f080", - "barcode": "f02a", - "bars": "f0c9", - "battery-0": "f244", - "battery-1": "f243", - "battery-2": "f242", - "battery-3": "f241", - "battery-4": "f240", - "battery-empty": "f244", - "battery-full": "f240", - "battery-half": "f242", - "battery-quarter": "f243", - "battery-three-quarters": "f241", - "bed": "f236", - "beer": "f0fc", - "behance": "f1b4", - "behance-square": "f1b5", - "bell": "f0f3", - "bell-o": "f0a2", - "bell-slash": "f1f6", - "bell-slash-o": "f1f7", - "bicycle": "f206", - "binoculars": "f1e5", - "birthday-cake": "f1fd", - "bitbucket": "f171", - "bitbucket-square": "f172", - "bitcoin": "f15a", - "black-tie": "f27e", - "bluetooth": "f293", - "bluetooth-b": "f294", - "bold": "f032", - "bolt": "f0e7", - "bomb": "f1e2", - "book": "f02d", - "bookmark": "f02e", - "bookmark-o": "f097", - "briefcase": "f0b1", - "btc": "f15a", - "bug": "f188", - "building": "f1ad", - "building-o": "f0f7", - "bullhorn": "f0a1", - "bullseye": "f140", - "bus": "f207", - "buysellads": "f20d", - "cab": "f1ba", - "calculator": "f1ec", - "calendar": "f073", - "calendar-check-o": "f274", - "calendar-minus-o": "f272", - "calendar-o": "f133", - "calendar-plus-o": "f271", - "calendar-times-o": "f273", - "camera": "f030", - "camera-retro": "f083", - "car": "f1b9", - "caret-down": "f0d7", - "caret-left": "f0d9", - "caret-right": "f0da", - "caret-square-o-down": "f150", - "caret-square-o-left": "f191", - "caret-square-o-right": "f152", - "caret-square-o-up": "f151", - "caret-up": "f0d8", - "cart-arrow-down": "f218", - "cart-plus": "f217", - "cc": "f20a", - "cc-amex": "f1f3", - "cc-diners-club": "f24c", - "cc-discover": "f1f2", - "cc-jcb": "f24b", - "cc-mastercard": "f1f1", - "cc-paypal": "f1f4", - "cc-stripe": "f1f5", - "cc-visa": "f1f0", - "certificate": "f0a3", - "chain": "f0c1", - "chain-broken": "f127", - "check": "f00c", - "check-circle": "f058", - "check-circle-o": "f05d", - "check-square": "f14a", - "check-square-o": "f046", - "chevron-circle-down": "f13a", - "chevron-circle-left": "f137", - "chevron-circle-right": "f138", - "chevron-circle-up": "f139", - "chevron-down": "f078", - "chevron-left": "f053", - "chevron-right": "f054", - "chevron-up": "f077", - "child": "f1ae", - "chrome": "f268", - "circle": "f111", - "circle-o": "f10c", - "circle-o-notch": "f1ce", - "circle-thin": "f1db", - "clipboard": "f0ea", - "clock-o": "f017", - "clone": "f24d", - "close": "f00d", - "cloud": "f0c2", - "cloud-download": "f0ed", - "cloud-upload": "f0ee", - "cny": "f157", - "code": "f121", - "code-fork": "f126", - "codepen": "f1cb", - "codiepie": "f284", - "coffee": "f0f4", - "cog": "f013", - "cogs": "f085", - "columns": "f0db", - "comment": "f075", - "comment-o": "f0e5", - "commenting": "f27a", - "commenting-o": "f27b", - "comments": "f086", - "comments-o": "f0e6", - "compass": "f14e", - "compress": "f066", - "connectdevelop": "f20e", - "contao": "f26d", - "copy": "f0c5", - "copyright": "f1f9", - "creative-commons": "f25e", - "credit-card": "f09d", - "credit-card-alt": "f283", - "crop": "f125", - "crosshairs": "f05b", - "css3": "f13c", - "cube": "f1b2", - "cubes": "f1b3", - "cut": "f0c4", - "cutlery": "f0f5", - "dashboard": "f0e4", - "dashcube": "f210", - "database": "f1c0", - "dedent": "f03b", - "delicious": "f1a5", - "desktop": "f108", - "deviantart": "f1bd", - "diamond": "f219", - "digg": "f1a6", - "dollar": "f155", - "dot-circle-o": "f192", - "download": "f019", - "dribbble": "f17d", - "dropbox": "f16b", - "drupal": "f1a9", - "edge": "f282", - "edit": "f044", - "eject": "f052", - "ellipsis-h": "f141", - "ellipsis-v": "f142", - "empire": "f1d1", - "envelope": "f0e0", - "envelope-o": "f003", - "envelope-square": "f199", - "eraser": "f12d", - "eur": "f153", - "euro": "f153", - "exchange": "f0ec", - "exclamation": "f12a", - "exclamation-circle": "f06a", - "exclamation-triangle": "f071", - "expand": "f065", - "expeditedssl": "f23e", - "external-link": "f08e", - "external-link-square": "f14c", - "eye": "f06e", - "eye-slash": "f070", - "eyedropper": "f1fb", - "facebook": "f09a", - "facebook-f": "f09a", - "facebook-official": "f230", - "facebook-square": "f082", - "fast-backward": "f049", - "fast-forward": "f050", - "fax": "f1ac", - "feed": "f09e", - "female": "f182", - "fighter-jet": "f0fb", - "file": "f15b", - "file-archive-o": "f1c6", - "file-audio-o": "f1c7", - "file-code-o": "f1c9", - "file-excel-o": "f1c3", - "file-image-o": "f1c5", - "file-movie-o": "f1c8", - "file-o": "f016", - "file-pdf-o": "f1c1", - "file-photo-o": "f1c5", - "file-picture-o": "f1c5", - "file-powerpoint-o": "f1c4", - "file-sound-o": "f1c7", - "file-text": "f15c", - "file-text-o": "f0f6", - "file-video-o": "f1c8", - "file-word-o": "f1c2", - "file-zip-o": "f1c6", - "files-o": "f0c5", - "film": "f008", - "filter": "f0b0", - "fire": "f06d", - "fire-extinguisher": "f134", - "firefox": "f269", - "flag": "f024", - "flag-checkered": "f11e", - "flag-o": "f11d", - "flash": "f0e7", - "flask": "f0c3", - "flickr": "f16e", - "floppy-o": "f0c7", - "folder": "f07b", - "folder-o": "f114", - "folder-open": "f07c", - "folder-open-o": "f115", - "font": "f031", - "fonticons": "f280", - "fort-awesome": "f286", - "forumbee": "f211", - "forward": "f04e", - "foursquare": "f180", - "frown-o": "f119", - "futbol-o": "f1e3", - "gamepad": "f11b", - "gavel": "f0e3", - "gbp": "f154", - "ge": "f1d1", - "gear": "f013", - "gears": "f085", - "genderless": "f22d", - "get-pocket": "f265", - "gg": "f260", - "gg-circle": "f261", - "gift": "f06b", - "git": "f1d3", - "git-square": "f1d2", - "github": "f09b", - "github-alt": "f113", - "github-square": "f092", - "gittip": "f184", - "glass": "f000", - "globe": "f0ac", - "google": "f1a0", - "google-plus": "f0d5", - "google-plus-square": "f0d4", - "google-wallet": "f1ee", - "graduation-cap": "f19d", - "gratipay": "f184", - "group": "f0c0", - "h-square": "f0fd", - "hacker-news": "f1d4", - "hand-grab-o": "f255", - "hand-lizard-o": "f258", - "hand-o-down": "f0a7", - "hand-o-left": "f0a5", - "hand-o-right": "f0a4", - "hand-o-up": "f0a6", - "hand-paper-o": "f256", - "hand-peace-o": "f25b", - "hand-pointer-o": "f25a", - "hand-rock-o": "f255", - "hand-scissors-o": "f257", - "hand-spock-o": "f259", - "hand-stop-o": "f256", - "hashtag": "f292", - "hdd-o": "f0a0", - "header": "f1dc", - "headphones": "f025", - "heart": "f004", - "heart-o": "f08a", - "heartbeat": "f21e", - "history": "f1da", - "home": "f015", - "hospital-o": "f0f8", - "hotel": "f236", - "hourglass": "f254", - "hourglass-1": "f251", - "hourglass-2": "f252", - "hourglass-3": "f253", - "hourglass-end": "f253", - "hourglass-half": "f252", - "hourglass-o": "f250", - "hourglass-start": "f251", - "houzz": "f27c", - "html5": "f13b", - "i-cursor": "f246", - "ils": "f20b", - "image": "f03e", - "inbox": "f01c", - "indent": "f03c", - "industry": "f275", - "info": "f129", - "info-circle": "f05a", - "inr": "f156", - "instagram": "f16d", - "institution": "f19c", - "internet-explorer": "f26b", - "intersex": "f224", - "ioxhost": "f208", - "italic": "f033", - "joomla": "f1aa", - "jpy": "f157", - "jsfiddle": "f1cc", - "key": "f084", - "keyboard-o": "f11c", - "krw": "f159", - "language": "f1ab", - "laptop": "f109", - "lastfm": "f202", - "lastfm-square": "f203", - "leaf": "f06c", - "leanpub": "f212", - "legal": "f0e3", - "lemon-o": "f094", - "level-down": "f149", - "level-up": "f148", - "life-bouy": "f1cd", - "life-buoy": "f1cd", - "life-ring": "f1cd", - "life-saver": "f1cd", - "lightbulb-o": "f0eb", - "line-chart": "f201", - "link": "f0c1", - "linkedin": "f0e1", - "linkedin-square": "f08c", - "linux": "f17c", - "list": "f03a", - "list-alt": "f022", - "list-ol": "f0cb", - "list-ul": "f0ca", - "location-arrow": "f124", - "lock": "f023", - "long-arrow-down": "f175", - "long-arrow-left": "f177", - "long-arrow-right": "f178", - "long-arrow-up": "f176", - "magic": "f0d0", - "magnet": "f076", - "mail-forward": "f064", - "mail-reply": "f112", - "mail-reply-all": "f122", - "male": "f183", - "map": "f279", - "map-marker": "f041", - "map-o": "f278", - "map-pin": "f276", - "map-signs": "f277", - "mars": "f222", - "mars-double": "f227", - "mars-stroke": "f229", - "mars-stroke-h": "f22b", - "mars-stroke-v": "f22a", - "maxcdn": "f136", - "meanpath": "f20c", - "medium": "f23a", - "medkit": "f0fa", - "meh-o": "f11a", - "mercury": "f223", - "microphone": "f130", - "microphone-slash": "f131", - "minus": "f068", - "minus-circle": "f056", - "minus-square": "f146", - "minus-square-o": "f147", - "mixcloud": "f289", - "mobile": "f10b", - "mobile-phone": "f10b", - "modx": "f285", - "money": "f0d6", - "moon-o": "f186", - "mortar-board": "f19d", - "motorcycle": "f21c", - "mouse-pointer": "f245", - "music": "f001", - "navicon": "f0c9", - "neuter": "f22c", - "newspaper-o": "f1ea", - "object-group": "f247", - "object-ungroup": "f248", - "odnoklassniki": "f263", - "odnoklassniki-square": "f264", - "opencart": "f23d", - "openid": "f19b", - "opera": "f26a", - "optin-monster": "f23c", - "outdent": "f03b", - "pagelines": "f18c", - "paint-brush": "f1fc", - "paper-plane": "f1d8", - "paper-plane-o": "f1d9", - "paperclip": "f0c6", - "paragraph": "f1dd", - "paste": "f0ea", - "pause": "f04c", - "pause-circle": "f28b", - "pause-circle-o": "f28c", - "paw": "f1b0", - "paypal": "f1ed", - "pencil": "f040", - "pencil-square": "f14b", - "pencil-square-o": "f044", - "percent": "f295", - "phone": "f095", - "phone-square": "f098", - "photo": "f03e", - "picture-o": "f03e", - "pie-chart": "f200", - "pied-piper": "f1a7", - "pied-piper-alt": "f1a8", - "pinterest": "f0d2", - "pinterest-p": "f231", - "pinterest-square": "f0d3", - "plane": "f072", - "play": "f04b", - "play-circle": "f144", - "play-circle-o": "f01d", - "plug": "f1e6", - "plus": "f067", - "plus-circle": "f055", - "plus-square": "f0fe", - "plus-square-o": "f196", - "power-off": "f011", - "print": "f02f", - "product-hunt": "f288", - "puzzle-piece": "f12e", - "qq": "f1d6", - "qrcode": "f029", - "question": "f128", - "question-circle": "f059", - "quote-left": "f10d", - "quote-right": "f10e", - "ra": "f1d0", - "random": "f074", - "rebel": "f1d0", - "recycle": "f1b8", - "reddit": "f1a1", - "reddit-alien": "f281", - "reddit-square": "f1a2", - "refresh": "f021", - "registered": "f25d", - "remove": "f00d", - "renren": "f18b", - "reorder": "f0c9", - "repeat": "f01e", - "reply": "f112", - "reply-all": "f122", - "retweet": "f079", - "rmb": "f157", - "road": "f018", - "rocket": "f135", - "rotate-left": "f0e2", - "rotate-right": "f01e", - "rouble": "f158", - "rss": "f09e", - "rss-square": "f143", - "rub": "f158", - "ruble": "f158", - "rupee": "f156", - "safari": "f267", - "save": "f0c7", - "scissors": "f0c4", - "scribd": "f28a", - "search": "f002", - "search-minus": "f010", - "search-plus": "f00e", - "sellsy": "f213", - "send": "f1d8", - "send-o": "f1d9", - "server": "f233", - "share": "f064", - "share-alt": "f1e0", - "share-alt-square": "f1e1", - "share-square": "f14d", - "share-square-o": "f045", - "shekel": "f20b", - "sheqel": "f20b", - "shield": "f132", - "ship": "f21a", - "shirtsinbulk": "f214", - "shopping-bag": "f290", - "shopping-basket": "f291", - "shopping-cart": "f07a", - "sign-in": "f090", - "sign-out": "f08b", - "signal": "f012", - "simplybuilt": "f215", - "sitemap": "f0e8", - "skyatlas": "f216", - "skype": "f17e", - "slack": "f198", - "sliders": "f1de", - "slideshare": "f1e7", - "smile-o": "f118", - "soccer-ball-o": "f1e3", - "sort": "f0dc", - "sort-alpha-asc": "f15d", - "sort-alpha-desc": "f15e", - "sort-amount-asc": "f160", - "sort-amount-desc": "f161", - "sort-asc": "f0de", - "sort-desc": "f0dd", - "sort-down": "f0dd", - "sort-numeric-asc": "f162", - "sort-numeric-desc": "f163", - "sort-up": "f0de", - "soundcloud": "f1be", - "space-shuttle": "f197", - "spinner": "f110", - "spoon": "f1b1", - "spotify": "f1bc", - "square": "f0c8", - "square-o": "f096", - "stack-exchange": "f18d", - "stack-overflow": "f16c", - "star": "f005", - "star-half": "f089", - "star-half-empty": "f123", - "star-half-full": "f123", - "star-half-o": "f123", - "star-o": "f006", - "steam": "f1b6", - "steam-square": "f1b7", - "step-backward": "f048", - "step-forward": "f051", - "stethoscope": "f0f1", - "sticky-note": "f249", - "sticky-note-o": "f24a", - "stop": "f04d", - "stop-circle": "f28d", - "stop-circle-o": "f28e", - "street-view": "f21d", - "strikethrough": "f0cc", - "stumbleupon": "f1a4", - "stumbleupon-circle": "f1a3", - "subscript": "f12c", - "subway": "f239", - "suitcase": "f0f2", - "sun-o": "f185", - "superscript": "f12b", - "support": "f1cd", - "table": "f0ce", - "tablet": "f10a", - "tachometer": "f0e4", - "tag": "f02b", - "tags": "f02c", - "tasks": "f0ae", - "taxi": "f1ba", - "television": "f26c", - "tencent-weibo": "f1d5", - "terminal": "f120", - "text-height": "f034", - "text-width": "f035", - "th": "f00a", - "th-large": "f009", - "th-list": "f00b", - "thumb-tack": "f08d", - "thumbs-down": "f165", - "thumbs-o-down": "f088", - "thumbs-o-up": "f087", - "thumbs-up": "f164", - "ticket": "f145", - "times": "f00d", - "times-circle": "f057", - "times-circle-o": "f05c", - "tint": "f043", - "toggle-down": "f150", - "toggle-left": "f191", - "toggle-off": "f204", - "toggle-on": "f205", - "toggle-right": "f152", - "toggle-up": "f151", - "trademark": "f25c", - "train": "f238", - "transgender": "f224", - "transgender-alt": "f225", - "trash": "f1f8", - "trash-o": "f014", - "tree": "f1bb", - "trello": "f181", - "tripadvisor": "f262", - "trophy": "f091", - "truck": "f0d1", - "try": "f195", - "tty": "f1e4", - "tumblr": "f173", - "tumblr-square": "f174", - "turkish-lira": "f195", - "tv": "f26c", - "twitch": "f1e8", - "twitter": "f099", - "twitter-square": "f081", - "umbrella": "f0e9", - "underline": "f0cd", - "undo": "f0e2", - "university": "f19c", - "unlink": "f127", - "unlock": "f09c", - "unlock-alt": "f13e", - "unsorted": "f0dc", - "upload": "f093", - "usb": "f287", - "usd": "f155", - "user": "f007", - "user-md": "f0f0", - "user-plus": "f234", - "user-secret": "f21b", - "user-times": "f235", - "users": "f0c0", - "venus": "f221", - "venus-double": "f226", - "venus-mars": "f228", - "viacoin": "f237", - "video-camera": "f03d", - "vimeo": "f27d", - "vimeo-square": "f194", - "vine": "f1ca", - "vk": "f189", - "volume-down": "f027", - "volume-off": "f026", - "volume-up": "f028", - "warning": "f071", - "wechat": "f1d7", - "weibo": "f18a", - "weixin": "f1d7", - "whatsapp": "f232", - "wheelchair": "f193", - "wifi": "f1eb", - "wikipedia-w": "f266", - "windows": "f17a", - "won": "f159", - "wordpress": "f19a", - "wrench": "f0ad", - "xing": "f168", - "xing-square": "f169", - "y-combinator": "f23b", - "y-combinator-square": "f1d4", - "yahoo": "f19e", - "yc": "f23b", - "yc-square": "f1d4", - "yelp": "f1e9", - "yen": "f157", - "youtube": "f167", - "youtube-play": "f16a", - "youtube-square": "f166" -} \ No newline at end of file diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 26dea7951a..0000000000 Binary files a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/iconic_font.py b/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/iconic_font.py deleted file mode 100644 index ce95f9e74f..0000000000 --- a/client/ayon_core/tools/pyblish_pype/vendor/qtawesome/iconic_font.py +++ /dev/null @@ -1,286 +0,0 @@ -"""Classes handling iconic fonts""" - -from __future__ import print_function - -import json -import os - -from qtpy import QtCore, QtGui - - -_default_options = { - 'color': QtGui.QColor(50, 50, 50), - 'color_disabled': QtGui.QColor(150, 150, 150), - 'opacity': 1.0, - 'scale_factor': 1.0, -} - - -def set_global_defaults(**kwargs): - """Set global defaults for all icons""" - valid_options = ['active', 'animation', 'color', 'color_active', - 'color_disabled', 'color_selected', 'disabled', 'offset', - 'scale_factor', 'selected'] - for kw in kwargs: - if kw in valid_options: - _default_options[kw] = kwargs[kw] - else: - error = "Invalid option '{0}'".format(kw) - raise KeyError(error) - - -class CharIconPainter: - - """Char icon painter""" - - def paint(self, iconic, painter, rect, mode, state, options): - """Main paint method""" - for opt in options: - self._paint_icon(iconic, painter, rect, mode, state, opt) - - def _paint_icon(self, iconic, painter, rect, mode, state, options): - """Paint a single icon""" - painter.save() - color, char = options['color'], options['char'] - - if mode == QtGui.QIcon.Disabled: - color = options.get('color_disabled', color) - char = options.get('disabled', char) - elif mode == QtGui.QIcon.Active: - color = options.get('color_active', color) - char = options.get('active', char) - elif mode == QtGui.QIcon.Selected: - color = options.get('color_selected', color) - char = options.get('selected', char) - - painter.setPen(QtGui.QColor(color)) - # A 16 pixel-high icon yields a font size of 14, which is pixel perfect - # for font-awesome. 16 * 0.875 = 14 - # The reason for not using full-sized glyphs is the negative bearing of - # fonts. - draw_size = 0.875 * round(rect.height() * options['scale_factor']) - prefix = options['prefix'] - - # Animation setup hook - animation = options.get('animation') - if animation is not None: - animation.setup(self, painter, rect) - - painter.setFont(iconic.font(prefix, draw_size)) - if 'offset' in options: - rect = QtCore.QRect(rect) - rect.translate(options['offset'][0] * rect.width(), - options['offset'][1] * rect.height()) - - painter.setOpacity(options.get('opacity', 1.0)) - - painter.drawText(rect, - QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter, - char) - painter.restore() - - -class CharIconEngine(QtGui.QIconEngine): - - """Specialization of QtGui.QIconEngine used to draw font-based icons""" - - def __init__(self, iconic, painter, options): - super(CharIconEngine, self).__init__() - self.iconic = iconic - self.painter = painter - self.options = options - - def paint(self, painter, rect, mode, state): - self.painter.paint( - self.iconic, painter, rect, mode, state, self.options) - - def pixmap(self, size, mode, state): - pm = QtGui.QPixmap(size) - pm.fill(QtCore.Qt.transparent) - self.paint(QtGui.QPainter(pm), - QtCore.QRect(QtCore.QPoint(0, 0), size), - mode, - state) - return pm - - -class IconicFont(QtCore.QObject): - - """Main class for managing iconic fonts""" - - def __init__(self, *args): - """Constructor - - :param *args: tuples - Each positional argument is a tuple of 3 or 4 values - - The prefix string to be used when accessing a given font set - - The ttf font filename - - The json charmap filename - - Optionally, the directory containing these files. When not - provided, the files will be looked up in ./fonts/ - """ - super(IconicFont, self).__init__() - self.painter = CharIconPainter() - self.painters = {} - self.fontname = {} - self.charmap = {} - for fargs in args: - self.load_font(*fargs) - - def load_font(self, - prefix, - ttf_filename, - charmap_filename, - directory=None): - """Loads a font file and the associated charmap - - If `directory` is None, the files will be looked up in ./fonts/ - - Arguments - --------- - prefix: str - prefix string to be used when accessing a given font set - ttf_filename: str - ttf font filename - charmap_filename: str - charmap filename - directory: str or None, optional - directory for font and charmap files - """ - - def hook(obj): - result = {} - for key in obj: - result[key] = chr(int(obj[key], 16)) - return result - - if directory is None: - directory = os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'fonts') - - with open(os.path.join(directory, charmap_filename), 'r') as codes: - self.charmap[prefix] = json.load(codes, object_hook=hook) - - id_ = QtGui.QFontDatabase.addApplicationFont( - os.path.join(directory, ttf_filename)) - - loadedFontFamilies = QtGui.QFontDatabase.applicationFontFamilies(id_) - - if(loadedFontFamilies): - self.fontname[prefix] = loadedFontFamilies[0] - else: - print('Font is empty') - - def icon(self, *names, **kwargs): - """Returns a QtGui.QIcon object corresponding to the provided icon name - (including prefix) - - Arguments - --------- - names: list of str - icon name, of the form PREFIX.NAME - - options: dict - options to be passed to the icon painter - """ - options_list = kwargs.pop('options', [{}] * len(names)) - general_options = kwargs - - if len(options_list) != len(names): - error = '"options" must be a list of size {0}'.format(len(names)) - raise Exception(error) - - parsed_options = [] - for i in range(len(options_list)): - specific_options = options_list[i] - parsed_options.append(self._parse_options(specific_options, - general_options, - names[i])) - - # Process high level API - api_options = parsed_options - - return self._icon_by_painter(self.painter, api_options) - - def _parse_options(self, specific_options, general_options, name): - """ """ - options = dict(_default_options, **general_options) - options.update(specific_options) - - # Handle icons for states - icon_kw = ['disabled', 'active', 'selected', 'char'] - names = [options.get(kw, name) for kw in icon_kw] - prefix, chars = self._get_prefix_chars(names) - options.update(dict(zip(*(icon_kw, chars)))) - options.update({'prefix': prefix}) - - # Handle colors for states - color_kw = ['color_active', 'color_selected'] - colors = [options.get(kw, options['color']) for kw in color_kw] - options.update(dict(zip(*(color_kw, colors)))) - - return options - - def _get_prefix_chars(self, names): - """ """ - chars = [] - for name in names: - if '.' in name: - prefix, n = name.split('.') - if prefix in self.charmap: - if n in self.charmap[prefix]: - chars.append(self.charmap[prefix][n]) - else: - error = 'Invalid icon name "{0}" in font "{1}"'.format( - n, prefix) - raise Exception(error) - else: - error = 'Invalid font prefix "{0}"'.format(prefix) - raise Exception(error) - else: - raise Exception('Invalid icon name') - - return prefix, chars - - def font(self, prefix, size): - """Returns QtGui.QFont corresponding to the given prefix and size - - Arguments - --------- - prefix: str - prefix string of the loaded font - size: int - size for the font - """ - font = QtGui.QFont(self.fontname[prefix]) - font.setPixelSize(size) - return font - - def set_custom_icon(self, name, painter): - """Associates a user-provided CharIconPainter to an icon name - The custom icon can later be addressed by calling - icon('custom.NAME') where NAME is the provided name for that icon. - - Arguments - --------- - name: str - name of the custom icon - painter: CharIconPainter - The icon painter, implementing - `paint(self, iconic, painter, rect, mode, state, options)` - """ - self.painters[name] = painter - - def _custom_icon(self, name, **kwargs): - """Returns the custom icon corresponding to the given name""" - options = dict(_default_options, **kwargs) - if name in self.painters: - painter = self.painters[name] - return self._icon_by_painter(painter, options) - else: - return QtGui.QIcon() - - def _icon_by_painter(self, painter, options): - """Returns the icon corresponding to the given painter""" - engine = CharIconEngine(self, painter, options) - return QtGui.QIcon(engine) diff --git a/client/ayon_core/tools/pyblish_pype/version.py b/client/ayon_core/tools/pyblish_pype/version.py deleted file mode 100644 index 5f1dce8011..0000000000 --- a/client/ayon_core/tools/pyblish_pype/version.py +++ /dev/null @@ -1,11 +0,0 @@ - -VERSION_MAJOR = 2 -VERSION_MINOR = 9 -VERSION_PATCH = 0 - - -version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) -version = '%i.%i.%i' % version_info -__version__ = version - -__all__ = ['version', 'version_info', '__version__'] diff --git a/client/ayon_core/tools/pyblish_pype/view.py b/client/ayon_core/tools/pyblish_pype/view.py deleted file mode 100644 index cc6604fc63..0000000000 --- a/client/ayon_core/tools/pyblish_pype/view.py +++ /dev/null @@ -1,334 +0,0 @@ -from qtpy import QtCore, QtWidgets -from . import model -from .constants import Roles, EXPANDER_WIDTH -# Imported when used -widgets = None - - -def _import_widgets(): - global widgets - if widgets is None: - from . import widgets - - -class OverviewView(QtWidgets.QTreeView): - # An item is requesting to be toggled, with optional forced-state - toggled = QtCore.Signal(QtCore.QModelIndex, object) - show_perspective = QtCore.Signal(QtCore.QModelIndex) - - def __init__(self, parent=None): - super(OverviewView, self).__init__(parent) - - self.horizontalScrollBar().hide() - self.viewport().setAttribute(QtCore.Qt.WA_Hover, True) - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.setItemsExpandable(True) - self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - self.setHeaderHidden(True) - self.setRootIsDecorated(False) - self.setIndentation(0) - - def event(self, event): - if not event.type() == QtCore.QEvent.KeyPress: - return super(OverviewView, self).event(event) - - elif event.key() == QtCore.Qt.Key_Space: - for index in self.selectionModel().selectedIndexes(): - self.toggled.emit(index, None) - - return True - - elif event.key() == QtCore.Qt.Key_Backspace: - for index in self.selectionModel().selectedIndexes(): - self.toggled.emit(index, False) - - return True - - elif event.key() == QtCore.Qt.Key_Return: - for index in self.selectionModel().selectedIndexes(): - self.toggled.emit(index, True) - - return True - - return super(OverviewView, self).event(event) - - def focusOutEvent(self, event): - self.selectionModel().clear() - - def mouseReleaseEvent(self, event): - if event.button() in (QtCore.Qt.LeftButton, QtCore.Qt.RightButton): - # Deselect all group labels - indexes = self.selectionModel().selectedIndexes() - for index in indexes: - if index.data(Roles.TypeRole) == model.GroupType: - self.selectionModel().select( - index, QtCore.QItemSelectionModel.Deselect - ) - - return super(OverviewView, self).mouseReleaseEvent(event) - - -class PluginView(OverviewView): - def __init__(self, *args, **kwargs): - super(PluginView, self).__init__(*args, **kwargs) - self.clicked.connect(self.item_expand) - - def item_expand(self, index): - if index.data(Roles.TypeRole) == model.GroupType: - if self.isExpanded(index): - self.collapse(index) - else: - self.expand(index) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - indexes = self.selectionModel().selectedIndexes() - if len(indexes) == 1: - index = indexes[0] - pos_index = self.indexAt(event.pos()) - # If instance or Plugin and is selected - if ( - index == pos_index - and index.data(Roles.TypeRole) == model.PluginType - ): - if event.pos().x() < 20: - self.toggled.emit(index, None) - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(index) - - return super(PluginView, self).mouseReleaseEvent(event) - - -class InstanceView(OverviewView): - def __init__(self, *args, **kwargs): - super(InstanceView, self).__init__(*args, **kwargs) - self.setSortingEnabled(True) - self.sortByColumn(0, QtCore.Qt.AscendingOrder) - self.viewport().setMouseTracking(True) - self._pressed_group_index = None - self._pressed_expander = None - - def mouseMoveEvent(self, event): - index = self.indexAt(event.pos()) - if index.data(Roles.TypeRole) == model.GroupType: - self.update(index) - super(InstanceView, self).mouseMoveEvent(event) - - def item_expand(self, index, expand=None): - if expand is None: - expand = not self.isExpanded(index) - - if expand: - self.expand(index) - else: - self.collapse(index) - - def group_toggle(self, index): - if not index.isValid(): - return - model = index.model() - - chilren_indexes_checked = [] - chilren_indexes_unchecked = [] - for idx in range(model.rowCount(index)): - child_index = model.index(idx, 0, index) - if not child_index.data(Roles.IsEnabledRole): - continue - - if child_index.data(QtCore.Qt.CheckStateRole): - chilren_indexes_checked.append(child_index) - else: - chilren_indexes_unchecked.append(child_index) - - if chilren_indexes_checked: - to_change_indexes = chilren_indexes_checked - new_state = False - else: - to_change_indexes = chilren_indexes_unchecked - new_state = True - - for index in to_change_indexes: - model.setData(index, new_state, QtCore.Qt.CheckStateRole) - self.toggled.emit(index, new_state) - - def _mouse_press(self, event): - if event.button() != QtCore.Qt.LeftButton: - return - - self._pressed_group_index = None - self._pressed_expander = None - - pos_index = self.indexAt(event.pos()) - if not pos_index.isValid(): - return - - if pos_index.data(Roles.TypeRole) != model.InstanceType: - self._pressed_group_index = pos_index - if event.pos().x() < 20: - self._pressed_expander = True - else: - self._pressed_expander = False - - elif event.pos().x() < 20: - indexes = self.selectionModel().selectedIndexes() - any_checked = False - if len(indexes) <= 1: - return - - if pos_index in indexes: - for index in indexes: - if index.data(QtCore.Qt.CheckStateRole): - any_checked = True - break - - for index in indexes: - self.toggled.emit(index, not any_checked) - return True - self.toggled.emit(pos_index, not any_checked) - - def mousePressEvent(self, event): - if self._mouse_press(event): - return - return super(InstanceView, self).mousePressEvent(event) - - def _mouse_release(self, event, pressed_expander, pressed_index): - if event.button() != QtCore.Qt.LeftButton: - return - - pos_index = self.indexAt(event.pos()) - if not pos_index.isValid(): - return - - if pos_index.data(Roles.TypeRole) == model.InstanceType: - indexes = self.selectionModel().selectedIndexes() - if len(indexes) == 1 and indexes[0] == pos_index: - if event.pos().x() < 20: - self.toggled.emit(indexes[0], None) - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(indexes[0]) - return True - return - - if pressed_index != pos_index: - return - - if self.state() == QtWidgets.QTreeView.State.DragSelectingState: - indexes = self.selectionModel().selectedIndexes() - if len(indexes) != 1 or indexes[0] != pos_index: - return - - if event.pos().x() < EXPANDER_WIDTH: - if pressed_expander is True: - self.item_expand(pos_index) - return True - else: - if pressed_expander is False: - self.group_toggle(pos_index) - self.item_expand(pos_index, True) - return True - - def mouseReleaseEvent(self, event): - pressed_index = self._pressed_group_index - pressed_expander = self._pressed_expander is True - self._pressed_group_index = None - self._pressed_expander = None - result = self._mouse_release(event, pressed_expander, pressed_index) - if result: - return - return super(InstanceView, self).mouseReleaseEvent(event) - - -class TerminalView(QtWidgets.QTreeView): - # An item is requesting to be toggled, with optional forced-state - def __init__(self, parent=None): - super(TerminalView, self).__init__(parent) - self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.setAutoScroll(False) - self.setHeaderHidden(True) - self.setIndentation(0) - self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - self.verticalScrollBar().setSingleStep(10) - self.setRootIsDecorated(False) - - self.clicked.connect(self.item_expand) - - _import_widgets() - - def event(self, event): - if not event.type() == QtCore.QEvent.KeyPress: - return super(TerminalView, self).event(event) - - elif event.key() == QtCore.Qt.Key_Space: - for index in self.selectionModel().selectedIndexes(): - if self.isExpanded(index): - self.collapse(index) - else: - self.expand(index) - - elif event.key() == QtCore.Qt.Key_Backspace: - for index in self.selectionModel().selectedIndexes(): - self.collapse(index) - - elif event.key() == QtCore.Qt.Key_Return: - for index in self.selectionModel().selectedIndexes(): - self.expand(index) - - return super(TerminalView, self).event(event) - - def focusOutEvent(self, event): - self.selectionModel().clear() - - def item_expand(self, index): - if index.data(Roles.TypeRole) == model.TerminalLabelType: - if self.isExpanded(index): - self.collapse(index) - else: - self.expand(index) - self.model().layoutChanged.emit() - self.updateGeometry() - - def rowsInserted(self, parent, start, end): - """Automatically scroll to bottom on each new item added.""" - super(TerminalView, self).rowsInserted(parent, start, end) - self.updateGeometry() - self.scrollToBottom() - - def expand(self, index): - """Wrapper to set widget for expanded index.""" - model = index.model() - row_count = model.rowCount(index) - is_new = False - for child_idx in range(row_count): - child_index = model.index(child_idx, index.column(), index) - widget = self.indexWidget(child_index) - if widget is None: - is_new = True - msg = child_index.data(QtCore.Qt.DisplayRole) - widget = widgets.TerminalDetail(msg) - self.setIndexWidget(child_index, widget) - super(TerminalView, self).expand(index) - if is_new: - self.updateGeometries() - - def resizeEvent(self, event): - super(self.__class__, self).resizeEvent(event) - self.model().layoutChanged.emit() - - def sizeHint(self): - size = super(TerminalView, self).sizeHint() - height = ( - self.contentsMargins().top() - + self.contentsMargins().bottom() - ) - for idx_i in range(self.model().rowCount()): - index = self.model().index(idx_i, 0) - height += self.rowHeight(index) - if self.isExpanded(index): - for idx_j in range(index.model().rowCount(index)): - child_index = index.child(idx_j, 0) - height += self.rowHeight(child_index) - - size.setHeight(height) - return size diff --git a/client/ayon_core/tools/pyblish_pype/widgets.py b/client/ayon_core/tools/pyblish_pype/widgets.py deleted file mode 100644 index 6adcc55f06..0000000000 --- a/client/ayon_core/tools/pyblish_pype/widgets.py +++ /dev/null @@ -1,555 +0,0 @@ -import sys -from qtpy import QtCore, QtWidgets, QtGui -from . import model, delegate, view, awesome -from .constants import PluginStates, InstanceStates, Roles - - -class EllidableLabel(QtWidgets.QLabel): - def __init__(self, *args, **kwargs): - super(EllidableLabel, self).__init__(*args, **kwargs) - self.setObjectName("EllidableLabel") - - def paintEvent(self, event): - painter = QtGui.QPainter(self) - - metrics = QtGui.QFontMetrics(self.font()) - elided = metrics.elidedText( - self.text(), QtCore.Qt.ElideRight, self.width() - ) - painter.drawText(self.rect(), self.alignment(), elided) - - -class PerspectiveLabel(QtWidgets.QTextEdit): - def __init__(self, parent=None): - super(PerspectiveLabel, self).__init__(parent) - self.setObjectName("PerspectiveLabel") - - size_policy = self.sizePolicy() - size_policy.setHeightForWidth(True) - size_policy.setVerticalPolicy(QtWidgets.QSizePolicy.Preferred) - self.setSizePolicy(size_policy) - - self.textChanged.connect(self.on_text_changed) - - def on_text_changed(self, *args, **kwargs): - self.updateGeometry() - - def hasHeightForWidth(self): - return True - - def heightForWidth(self, width): - margins = self.contentsMargins() - - document_width = 0 - if width >= margins.left() + margins.right(): - document_width = width - margins.left() - margins.right() - - document = self.document().clone() - document.setTextWidth(document_width) - - return margins.top() + document.size().height() + margins.bottom() - - def sizeHint(self): - width = super(PerspectiveLabel, self).sizeHint().width() - return QtCore.QSize(width, self.heightForWidth(width)) - - -class PerspectiveWidget(QtWidgets.QWidget): - l_doc = "Documentation" - l_rec = "Records" - l_path = "Path" - - def __init__(self, parent): - super(PerspectiveWidget, self).__init__(parent) - - self.parent_widget = parent - main_layout = QtWidgets.QVBoxLayout(self) - - header_widget = QtWidgets.QWidget() - toggle_button = QtWidgets.QPushButton(parent=header_widget) - toggle_button.setObjectName("PerspectiveToggleBtn") - toggle_button.setText(delegate.icons["angle-left"]) - toggle_button.setMinimumHeight(50) - toggle_button.setFixedWidth(40) - - indicator = QtWidgets.QLabel("", parent=header_widget) - indicator.setFixedWidth(30) - indicator.setAlignment(QtCore.Qt.AlignCenter) - indicator.setObjectName("PerspectiveIndicator") - - name = EllidableLabel('*Name of inspected', parent=header_widget) - - header_layout = QtWidgets.QHBoxLayout(header_widget) - header_layout.setAlignment(QtCore.Qt.AlignLeft) - header_layout.addWidget(toggle_button) - header_layout.addWidget(indicator) - header_layout.addWidget(name) - header_layout.setContentsMargins(0, 0, 0, 0) - header_layout.setSpacing(10) - header_widget.setLayout(header_layout) - - main_layout.setAlignment(QtCore.Qt.AlignTop) - main_layout.addWidget(header_widget) - - scroll_widget = QtWidgets.QScrollArea(self) - scroll_widget.setObjectName("PerspectiveScrollContent") - - contents_widget = QtWidgets.QWidget(scroll_widget) - contents_widget.setObjectName("PerspectiveWidgetContent") - - layout = QtWidgets.QVBoxLayout() - layout.setAlignment(QtCore.Qt.AlignTop) - layout.setContentsMargins(0, 0, 0, 0) - - documentation = ExpandableWidget(self, self.l_doc) - doc_label = PerspectiveLabel() - documentation.set_content(doc_label) - layout.addWidget(documentation) - - path = ExpandableWidget(self, self.l_path) - path_label = PerspectiveLabel() - path.set_content(path_label) - layout.addWidget(path) - - records = ExpandableWidget(self, self.l_rec) - layout.addWidget(records) - - contents_widget.setLayout(layout) - - terminal_view = view.TerminalView() - terminal_view.setObjectName("TerminalView") - terminal_model = model.TerminalModel() - terminal_proxy = model.TerminalProxy(terminal_view) - terminal_proxy.setSourceModel(terminal_model) - - terminal_view.setModel(terminal_proxy) - terminal_delegate = delegate.TerminalItem() - terminal_view.setItemDelegate(terminal_delegate) - records.set_content(terminal_view) - - scroll_widget.setWidgetResizable(True) - scroll_widget.setWidget(contents_widget) - - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - main_layout.addWidget(scroll_widget) - self.setLayout(main_layout) - - self.terminal_view = terminal_view - self.terminal_model = terminal_model - self.terminal_proxy = terminal_proxy - - self.indicator = indicator - self.scroll_widget = scroll_widget - self.contents_widget = contents_widget - self.toggle_button = toggle_button - self.name_widget = name - self.documentation = documentation - self.path = path - self.records = records - - self.toggle_button.clicked.connect(self.toggle_me) - - self.last_type = None - self.last_item_id = None - self.last_id = None - - def trim(self, docstring): - if not docstring: - return "" - # Convert tabs to spaces (following the normal Python rules) - # and split into a list of lines: - lines = docstring.expandtabs().splitlines() - # Determine minimum indentation (first line doesn't count): - try: - indent = sys.maxint - max = sys.maxint - except Exception: - indent = sys.maxsize - max = sys.maxsize - - for line in lines[1:]: - stripped = line.lstrip() - if stripped: - indent = min(indent, len(line) - len(stripped)) - # Remove indentation (first line is special): - trimmed = [lines[0].strip()] - if indent < max: - for line in lines[1:]: - trimmed.append(line[indent:].rstrip()) - # Strip off trailing and leading blank lines: - while trimmed and not trimmed[-1]: - trimmed.pop() - while trimmed and not trimmed[0]: - trimmed.pop(0) - # Return a single string: - return "\n".join(trimmed) - - def set_indicator_state(self, state): - self.indicator.setProperty("state", state) - self.indicator.style().polish(self.indicator) - - def reset(self): - self.last_id = None - self.set_records(list()) - self.set_indicator_state(None) - - def update_context(self, plugin_item, instance_item): - if not self.last_item_id or not self.last_type: - return - - if self.last_type == model.PluginType: - if not self.last_id: - _item_id = plugin_item.data(Roles.ObjectUIdRole) - if _item_id != self.last_item_id: - return - self.last_id = plugin_item.plugin.id - - elif self.last_id != plugin_item.plugin.id: - return - - self.set_context(plugin_item.index()) - return - - if self.last_type == model.InstanceType: - if not self.last_id: - _item_id = instance_item.data(Roles.ObjectUIdRole) - if _item_id != self.last_item_id: - return - self.last_id = instance_item.instance.id - - elif self.last_id != instance_item.instance.id: - return - - self.set_context(instance_item.index()) - return - - def set_context(self, index): - if not index or not index.isValid(): - index_type = None - else: - index_type = index.data(Roles.TypeRole) - - if index_type == model.InstanceType: - item_id = index.data(Roles.ObjectIdRole) - publish_states = index.data(Roles.PublishFlagsRole) - if publish_states & InstanceStates.ContextType: - type_indicator = "C" - else: - type_indicator = "I" - - if publish_states & InstanceStates.InProgress: - self.set_indicator_state("active") - - elif publish_states & InstanceStates.HasError: - self.set_indicator_state("error") - - elif publish_states & InstanceStates.HasWarning: - self.set_indicator_state("warning") - - elif publish_states & InstanceStates.HasFinished: - self.set_indicator_state("ok") - else: - self.set_indicator_state(None) - - self.documentation.setVisible(False) - self.path.setVisible(False) - - elif index_type == model.PluginType: - item_id = index.data(Roles.ObjectIdRole) - type_indicator = "P" - - doc = index.data(Roles.DocstringRole) - doc_str = "" - if doc: - doc_str = self.trim(doc) - - publish_states = index.data(Roles.PublishFlagsRole) - if publish_states & PluginStates.InProgress: - self.set_indicator_state("active") - - elif publish_states & PluginStates.HasError: - self.set_indicator_state("error") - - elif publish_states & PluginStates.HasWarning: - self.set_indicator_state("warning") - - elif publish_states & PluginStates.WasProcessed: - self.set_indicator_state("ok") - - else: - self.set_indicator_state(None) - - self.documentation.toggle_content(bool(doc_str)) - self.documentation.content.setText(doc_str) - - path = index.data(Roles.PathModuleRole) or "" - self.path.toggle_content(path.strip() != "") - self.path.content.setText(path) - - self.documentation.setVisible(True) - self.path.setVisible(True) - - else: - self.last_type = None - self.last_id = None - self.indicator.setText("?") - self.set_indicator_state(None) - self.documentation.setVisible(False) - self.path.setVisible(False) - self.records.setVisible(False) - return - - self.last_type = index_type - self.last_id = item_id - self.last_item_id = index.data(Roles.ObjectUIdRole) - - self.indicator.setText(type_indicator) - - label = index.data(QtCore.Qt.DisplayRole) - self.name_widget.setText(label) - self.records.setVisible(True) - - records = index.data(Roles.LogRecordsRole) or [] - self.set_records(records) - - def set_records(self, records): - len_records = 0 - if records: - len_records += len(records) - - data = {"records": records} - self.terminal_model.reset() - self.terminal_model.update_with_result(data) - - self.records.button_toggle_text.setText( - "{} ({})".format(self.l_rec, len_records) - ) - self.records.toggle_content(len_records > 0) - - def toggle_me(self): - self.parent_widget.parent().toggle_perspective_widget() - - -class ClickableWidget(QtWidgets.QLabel): - clicked = QtCore.Signal() - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self.clicked.emit() - super(ClickableWidget, self).mouseReleaseEvent(event) - - -class ExpandableWidget(QtWidgets.QWidget): - - content = None - - def __init__(self, parent, title): - super(ExpandableWidget, self).__init__(parent) - - top_part = ClickableWidget(parent=self) - top_part.setObjectName("ExpandableHeader") - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - button_toggle_text = QtWidgets.QLabel(title, parent=top_part) - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(button_toggle_text) - top_part.setLayout(layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 0) - - content = QtWidgets.QFrame(self) - content.setObjectName("ExpandableWidgetContent") - content.setVisible(False) - - content_layout = QtWidgets.QVBoxLayout(content) - - main_layout.addWidget(top_part) - main_layout.addWidget(content) - self.setLayout(main_layout) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.top_part = top_part - self.button_toggle = button_toggle - self.button_toggle_text = button_toggle_text - - self.content_widget = content - self.content_layout = content_layout - - self.top_part.clicked.connect(self.top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - - def top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - - def resizeEvent(self, event): - super(ExpandableWidget, self).resizeEvent(event) - self.content.updateGeometry() - - def set_content(self, in_widget): - if self.content: - self.content.hide() - self.content_layout.removeWidget(self.content) - self.content_layout.addWidget(in_widget) - self.content = in_widget - - -class ButtonWithMenu(QtWidgets.QWidget): - def __init__(self, button_title, parent=None): - super(ButtonWithMenu, self).__init__(parent=parent) - self.setSizePolicy(QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum - )) - - self.layout = QtWidgets.QHBoxLayout(self) - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(0) - - self.menu = QtWidgets.QMenu() - # TODO move to stylesheets - self.menu.setStyleSheet(""" - *{color: #fff; background-color: #555; border: 1px solid #222;} - ::item {background-color: transparent;padding: 5px; - padding-left: 10px;padding-right: 10px;} - ::item:selected {background-color: #666;} - """) - - self.button = QtWidgets.QPushButton(button_title) - self.button.setObjectName("ButtonWithMenu") - - self.layout.addWidget(self.button) - - self.button.clicked.connect(self.btn_clicked) - - def btn_clicked(self): - self.menu.popup(self.button.mapToGlobal( - QtCore.QPoint(0, self.button.height()) - )) - - def addItem(self, text, callback): - self.menu.addAction(text, callback) - self.button.setToolTip("Select to apply predefined presets") - - def clearMenu(self): - self.menu.clear() - self.button.setToolTip("Presets not found") - - -class CommentBox(QtWidgets.QLineEdit): - - def __init__(self, placeholder_text, parent=None): - super(CommentBox, self).__init__(parent=parent) - self.placeholder = QtWidgets.QLabel(placeholder_text, self) - self.placeholder.move(2, 2) - - def focusInEvent(self, event): - self.placeholder.setVisible(False) - return super(CommentBox, self).focusInEvent(event) - - def focusOutEvent(self, event): - current_text = self.text() - current_text = current_text.strip(" ") - self.setText(current_text) - if not self.text(): - self.placeholder.setVisible(True) - return super(CommentBox, self).focusOutEvent(event) - - -class TerminalDetail(QtWidgets.QTextEdit): - def __init__(self, text, *args, **kwargs): - super(TerminalDetail, self).__init__(*args, **kwargs) - - self.setReadOnly(True) - self.setHtml(text) - self.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - self.setWordWrapMode( - QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere - ) - - def sizeHint(self): - content_margins = ( - self.contentsMargins().top() - + self.contentsMargins().bottom() - ) - size = self.document().documentLayout().documentSize().toSize() - size.setHeight(size.height() + content_margins) - return size - - -class FilterButton(QtWidgets.QPushButton): - def __init__(self, name, *args, **kwargs): - self.filter_name = name - - super(FilterButton, self).__init__(*args, **kwargs) - - self.toggled.connect(self.on_toggle) - - self.setProperty("type", name) - self.setObjectName("TerminalFilerBtn") - self.setCheckable(True) - self.setChecked( - model.TerminalProxy.filter_buttons_checks[name] - ) - - def on_toggle(self, toggle_state): - model.TerminalProxy.change_filter(self.filter_name, toggle_state) - - -class TerminalFilterWidget(QtWidgets.QWidget): - # timer.timeout.connect(lambda: self._update(self.parent_widget)) - def __init__(self, *args, **kwargs): - super(TerminalFilterWidget, self).__init__(*args, **kwargs) - self.setObjectName("TerminalFilterWidget") - self.filter_changed = QtCore.Signal() - - info_icon = awesome.tags["info"] - log_icon = awesome.tags["circle"] - error_icon = awesome.tags["exclamation-triangle"] - - filter_buttons = ( - FilterButton("info", info_icon, self), - FilterButton("log_debug", log_icon, self), - FilterButton("log_info", log_icon, self), - FilterButton("log_warning", log_icon, self), - FilterButton("log_error", log_icon, self), - FilterButton("log_critical", log_icon, self), - FilterButton("error", error_icon, self) - ) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - # Add spacers - spacer = QtWidgets.QWidget() - spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) - layout.addWidget(spacer, 1) - - for btn in filter_buttons: - layout.addWidget(btn) - - self.setLayout(layout) - - self.filter_buttons = filter_buttons diff --git a/client/ayon_core/tools/pyblish_pype/window.py b/client/ayon_core/tools/pyblish_pype/window.py deleted file mode 100644 index 01d373d841..0000000000 --- a/client/ayon_core/tools/pyblish_pype/window.py +++ /dev/null @@ -1,1315 +0,0 @@ -"""Main Window - -States: - These are all possible states and their transitions. - - - reset - ' - ' - ' - ___v__ - | | reset - | Idle |--------------------. - | |<-------------------' - | | - | | _____________ - | | validate | | reset # TODO - | |----------------->| In-progress |-----------. - | | |_____________| ' - | |<-------------------------------------------' - | | - | | _____________ - | | publish | | - | |----------------->| In-progress |---. - | | |_____________| ' - | |<-----------------------------------' - |______| - - -Todo: - There are notes spread throughout this project with the syntax: - - - TODO(username) - - The `username` is a quick and dirty indicator of who made the note - and is by no means exclusive to that person in terms of seeing it - done. Feel free to do, or make your own TODO's as you code. Just - make sure the description is sufficient for anyone reading it for - the first time to understand how to actually to it! - -""" -import sys -from functools import partial - -from . import delegate, model, settings, util, view, widgets -from .awesome import tags as awesome - -from qtpy import QtCore, QtGui, QtWidgets -from .constants import ( - PluginStates, PluginActionStates, InstanceStates, GroupStates, Roles -) -if sys.version_info[0] == 3: - from queue import Queue -else: - from Queue import Queue - - -class Window(QtWidgets.QDialog): - def __init__(self, controller, parent=None): - super(Window, self).__init__(parent=parent) - - self._suspend_logs = False - - # Use plastique style for specific ocations - # TODO set style name via environment variable - low_keys = { - key.lower(): key - for key in QtWidgets.QStyleFactory.keys() - } - if "plastique" in low_keys: - self.setStyle( - QtWidgets.QStyleFactory.create(low_keys["plastique"]) - ) - - icon = QtGui.QIcon(util.get_asset("img", "logo-extrasmall.png")) - if parent is None: - on_top_flag = QtCore.Qt.WindowStaysOnTopHint - else: - on_top_flag = QtCore.Qt.Dialog - - self.setWindowFlags( - self.windowFlags() - | QtCore.Qt.WindowTitleHint - | QtCore.Qt.WindowMaximizeButtonHint - | QtCore.Qt.WindowMinimizeButtonHint - | QtCore.Qt.WindowCloseButtonHint - | on_top_flag - ) - self.setWindowIcon(icon) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - - self.controller = controller - - main_widget = QtWidgets.QWidget(self) - - # General layout - header_widget = QtWidgets.QWidget(parent=main_widget) - - header_tab_widget = QtWidgets.QWidget(header_widget) - header_tab_overview = QtWidgets.QRadioButton(header_tab_widget) - header_tab_terminal = QtWidgets.QRadioButton(header_tab_widget) - header_spacer = QtWidgets.QWidget(header_tab_widget) - - button_suspend_logs_widget = QtWidgets.QWidget() - button_suspend_logs_widget_layout = QtWidgets.QHBoxLayout( - button_suspend_logs_widget - ) - button_suspend_logs_widget_layout.setContentsMargins(0, 10, 0, 10) - button_suspend_logs = QtWidgets.QPushButton(header_widget) - button_suspend_logs.setFixedWidth(7) - button_suspend_logs.setSizePolicy( - QtWidgets.QSizePolicy.Preferred, - QtWidgets.QSizePolicy.Expanding - ) - button_suspend_logs_widget_layout.addWidget(button_suspend_logs) - header_aditional_btns = QtWidgets.QWidget(header_tab_widget) - - aditional_btns_layout = QtWidgets.QHBoxLayout(header_aditional_btns) - - presets_button = widgets.ButtonWithMenu(awesome["filter"]) - presets_button.setEnabled(False) - aditional_btns_layout.addWidget(presets_button) - - layout_tab = QtWidgets.QHBoxLayout(header_tab_widget) - layout_tab.setContentsMargins(0, 0, 0, 0) - layout_tab.setSpacing(0) - layout_tab.addWidget(header_tab_overview, 0) - layout_tab.addWidget(header_tab_terminal, 0) - layout_tab.addWidget(button_suspend_logs_widget, 0) - - # Compress items to the left - layout_tab.addWidget(header_spacer, 1) - layout_tab.addWidget(header_aditional_btns, 0) - - layout = QtWidgets.QHBoxLayout(header_widget) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(header_tab_widget) - - header_widget.setLayout(layout) - - # Overview Page - # TODO add parent - overview_page = QtWidgets.QWidget() - - overview_instance_view = view.InstanceView(parent=overview_page) - overview_instance_view.setAnimated(settings.Animated) - overview_instance_delegate = delegate.InstanceDelegate( - parent=overview_instance_view - ) - instance_model = model.InstanceModel(controller) - instance_sort_proxy = model.InstanceSortProxy() - instance_sort_proxy.setSourceModel(instance_model) - - overview_instance_view.setItemDelegate(overview_instance_delegate) - overview_instance_view.setModel(instance_sort_proxy) - - overview_plugin_view = view.PluginView(parent=overview_page) - overview_plugin_view.setAnimated(settings.Animated) - overview_plugin_delegate = delegate.PluginDelegate( - parent=overview_plugin_view - ) - overview_plugin_view.setItemDelegate(overview_plugin_delegate) - plugin_model = model.PluginModel(controller) - plugin_proxy = model.PluginFilterProxy() - plugin_proxy.setSourceModel(plugin_model) - overview_plugin_view.setModel(plugin_proxy) - - layout = QtWidgets.QHBoxLayout(overview_page) - layout.addWidget(overview_instance_view, 1) - layout.addWidget(overview_plugin_view, 1) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(0) - overview_page.setLayout(layout) - - # Terminal - terminal_container = QtWidgets.QWidget() - - terminal_view = view.TerminalView() - terminal_model = model.TerminalModel() - terminal_proxy = model.TerminalProxy(terminal_view) - terminal_proxy.setSourceModel(terminal_model) - - terminal_view.setModel(terminal_proxy) - terminal_delegate = delegate.TerminalItem() - terminal_view.setItemDelegate(terminal_delegate) - - layout = QtWidgets.QVBoxLayout(terminal_container) - layout.addWidget(terminal_view) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(0) - - terminal_container.setLayout(layout) - - terminal_page = QtWidgets.QWidget() - layout = QtWidgets.QVBoxLayout(terminal_page) - layout.addWidget(terminal_container) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - # Add some room between window borders and contents - body_widget = QtWidgets.QWidget(main_widget) - layout = QtWidgets.QHBoxLayout(body_widget) - layout.setContentsMargins(5, 5, 5, 1) - layout.addWidget(overview_page) - layout.addWidget(terminal_page) - - # Comment Box - comment_box = widgets.CommentBox("Comment...", self) - - intent_box = QtWidgets.QComboBox() - - intent_model = model.IntentModel() - intent_box.setModel(intent_model) - - comment_intent_widget = QtWidgets.QWidget() - comment_intent_layout = QtWidgets.QHBoxLayout(comment_intent_widget) - comment_intent_layout.setContentsMargins(0, 0, 0, 0) - comment_intent_layout.setSpacing(5) - comment_intent_layout.addWidget(comment_box) - comment_intent_layout.addWidget(intent_box) - - # Terminal filtering - terminal_filters_widget = widgets.TerminalFilterWidget() - - # Footer - footer_widget = QtWidgets.QWidget(main_widget) - - footer_info = QtWidgets.QLabel(footer_widget) - footer_spacer = QtWidgets.QWidget(footer_widget) - - footer_button_stop = QtWidgets.QPushButton( - awesome["stop"], footer_widget - ) - footer_button_stop.setToolTip("Stop publishing") - footer_button_reset = QtWidgets.QPushButton( - awesome["refresh"], footer_widget - ) - footer_button_reset.setToolTip("Restart publishing") - footer_button_validate = QtWidgets.QPushButton( - awesome["flask"], footer_widget - ) - footer_button_validate.setToolTip("Run validations") - footer_button_play = QtWidgets.QPushButton( - awesome["play"], footer_widget - ) - footer_button_play.setToolTip("Publish") - layout = QtWidgets.QHBoxLayout() - layout.setContentsMargins(5, 5, 5, 5) - layout.addWidget(footer_info, 0) - layout.addWidget(footer_spacer, 1) - - layout.addWidget(footer_button_stop, 0) - layout.addWidget(footer_button_reset, 0) - layout.addWidget(footer_button_validate, 0) - layout.addWidget(footer_button_play, 0) - - footer_layout = QtWidgets.QVBoxLayout(footer_widget) - footer_layout.addWidget(terminal_filters_widget) - footer_layout.addWidget(comment_intent_widget) - footer_layout.addLayout(layout) - - footer_widget.setProperty("success", -1) - - # Placeholder for when GUI is closing - # TODO(marcus): Fade to black and the the user about what's happening - closing_placeholder = QtWidgets.QWidget(main_widget) - closing_placeholder.setSizePolicy( - QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding - ) - closing_placeholder.hide() - - perspective_widget = widgets.PerspectiveWidget(main_widget) - perspective_widget.hide() - - pages_widget = QtWidgets.QWidget(main_widget) - layout = QtWidgets.QVBoxLayout(pages_widget) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addWidget(header_widget, 0) - layout.addWidget(body_widget, 1) - - # Main layout - layout = QtWidgets.QVBoxLayout(main_widget) - layout.addWidget(pages_widget, 3) - layout.addWidget(perspective_widget, 3) - layout.addWidget(closing_placeholder, 1) - layout.addWidget(footer_widget, 0) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - main_widget.setLayout(layout) - - self.main_layout = QtWidgets.QVBoxLayout(self) - self.main_layout.setContentsMargins(0, 0, 0, 0) - self.main_layout.setSpacing(0) - self.main_layout.addWidget(main_widget) - - """Setup - - Widgets are referred to in CSS via their object-name. We - use the same mechanism internally to refer to objects; so rather - than storing widgets as self.my_widget, it is referred to as: - - >>> my_widget = self.findChild(QtWidgets.QWidget, "MyWidget") - - This way there is only ever a single method of referring to any widget. - """ - - names = { - # Main - "Header": header_widget, - "Body": body_widget, - "Footer": footer_widget, - - # Pages - "Overview": overview_page, - "Terminal": terminal_page, - - # Tabs - "OverviewTab": header_tab_overview, - "TerminalTab": header_tab_terminal, - - # Views - "TerminalView": terminal_view, - - # Buttons - "SuspendLogsBtn": button_suspend_logs, - "Stop": footer_button_stop, - "Reset": footer_button_reset, - "Validate": footer_button_validate, - "Play": footer_button_play, - - # Misc - "HeaderSpacer": header_spacer, - "FooterSpacer": footer_spacer, - "FooterInfo": footer_info, - "CommentIntentWidget": comment_intent_widget, - "CommentBox": comment_box, - "CommentPlaceholder": comment_box.placeholder, - "ClosingPlaceholder": closing_placeholder, - "IntentBox": intent_box - } - - for name, _widget in names.items(): - _widget.setObjectName(name) - - # Enable CSS on plain QWidget objects - for _widget in ( - pages_widget, - header_widget, - body_widget, - comment_box, - overview_page, - terminal_page, - footer_widget, - button_suspend_logs, - footer_button_stop, - footer_button_reset, - footer_button_validate, - footer_button_play, - footer_spacer, - closing_placeholder - ): - _widget.setAttribute(QtCore.Qt.WA_StyledBackground) - - # Signals - header_tab_overview.toggled.connect( - lambda: self.on_tab_changed("overview") - ) - header_tab_terminal.toggled.connect( - lambda: self.on_tab_changed("terminal") - ) - - overview_instance_view.show_perspective.connect( - self.toggle_perspective_widget - ) - overview_plugin_view.show_perspective.connect( - self.toggle_perspective_widget - ) - - controller.switch_toggleability.connect(self.change_toggleability) - - controller.was_reset.connect(self.on_was_reset) - # This is called synchronously on each process - controller.was_processed.connect(self.on_was_processed) - controller.passed_group.connect(self.on_passed_group) - controller.was_stopped.connect(self.on_was_stopped) - controller.was_finished.connect(self.on_was_finished) - - controller.was_skipped.connect(self.on_was_skipped) - controller.was_acted.connect(self.on_was_acted) - - # NOTE: Listeners to this signal are run in the main thread - controller.about_to_process.connect( - self.on_about_to_process, - QtCore.Qt.DirectConnection - ) - - overview_instance_view.toggled.connect(self.on_instance_toggle) - overview_plugin_view.toggled.connect(self.on_plugin_toggle) - - button_suspend_logs.clicked.connect(self.on_suspend_clicked) - footer_button_stop.clicked.connect(self.on_stop_clicked) - footer_button_reset.clicked.connect(self.on_reset_clicked) - footer_button_validate.clicked.connect(self.on_validate_clicked) - footer_button_play.clicked.connect(self.on_play_clicked) - - comment_box.textChanged.connect(self.on_comment_entered) - comment_box.returnPressed.connect(self.on_play_clicked) - overview_plugin_view.customContextMenuRequested.connect( - self.on_plugin_action_menu_requested - ) - - instance_model.group_created.connect(self.on_instance_group_created) - - self.main_widget = main_widget - - self.pages_widget = pages_widget - self.header_widget = header_widget - self.body_widget = body_widget - - self.terminal_filters_widget = terminal_filters_widget - - self.footer_widget = footer_widget - self.button_suspend_logs = button_suspend_logs - self.footer_button_stop = footer_button_stop - self.footer_button_reset = footer_button_reset - self.footer_button_validate = footer_button_validate - self.footer_button_play = footer_button_play - - self.footer_info = footer_info - - self.overview_instance_view = overview_instance_view - self.overview_plugin_view = overview_plugin_view - self.plugin_model = plugin_model - self.plugin_proxy = plugin_proxy - self.instance_model = instance_model - self.instance_sort_proxy = instance_sort_proxy - - self.presets_button = presets_button - - self.terminal_model = terminal_model - self.terminal_proxy = terminal_proxy - self.terminal_view = terminal_view - - self.comment_main_widget = comment_intent_widget - self.comment_box = comment_box - self.intent_box = intent_box - self.intent_model = intent_model - - self.perspective_widget = perspective_widget - - self.tabs = { - "overview": header_tab_overview, - "terminal": header_tab_terminal - } - self.pages = ( - ("overview", overview_page), - ("terminal", terminal_page) - ) - - current_page = settings.InitialTab or "overview" - self.comment_main_widget.setVisible( - not current_page == "terminal" - ) - self.terminal_filters_widget.setVisible( - current_page == "terminal" - ) - - self._current_page = current_page - self._hidden_for_plugin_process = False - - self.tabs[current_page].setChecked(True) - - self.apply_log_suspend_value( - util.env_variable_to_bool("PYBLISH_SUSPEND_LOGS") - ) - - # ------------------------------------------------------------------------- - # - # Event handlers - # - # ------------------------------------------------------------------------- - def set_presets(self, key): - plugin_settings = self.controller.possible_presets.get(key) - if not plugin_settings: - return - - for plugin_item in self.plugin_model.plugin_items.values(): - if not plugin_item.plugin.optional: - continue - - value = plugin_settings.get( - plugin_item.plugin.__name__, - # if plugin is not in presets then set default value - self.controller.optional_default.get( - plugin_item.plugin.__name__ - ) - ) - if value is None: - continue - - plugin_item.setData(value, QtCore.Qt.CheckStateRole) - - def toggle_perspective_widget(self, index=None): - show = False - if index: - show = True - self.perspective_widget.set_context(index) - - self.pages_widget.setVisible(not show) - self.perspective_widget.setVisible(show) - self.footer_items_visibility() - - def change_toggleability(self, enable_value): - for plugin_item in self.plugin_model.plugin_items.values(): - plugin_item.setData(enable_value, Roles.IsEnabledRole) - - for instance_item in ( - self.instance_model.instance_items.values() - ): - instance_item.setData(enable_value, Roles.IsEnabledRole) - - def _add_intent_to_context(self): - context_value = None - if ( - self.intent_model.has_items - and "intent" not in self.controller.context.data - ): - idx = self.intent_model.index(self.intent_box.currentIndex(), 0) - intent_value = self.intent_model.data(idx, Roles.IntentItemValue) - intent_label = self.intent_model.data(idx, QtCore.Qt.DisplayRole) - if intent_value: - context_value = { - "value": intent_value, - "label": intent_label - } - - # Unset intent if is set to empty value - if context_value is None: - self.controller.context.data.pop("intent", None) - else: - self.controller.context.data["intent"] = context_value - - def on_instance_toggle(self, index, state=None): - """An item is requesting to be toggled""" - if not index.data(Roles.IsOptionalRole): - return self.info("This item is mandatory") - - if self.controller.collect_state != 1: - return self.info("Cannot toggle") - - current_state = index.data(QtCore.Qt.CheckStateRole) - if state is None: - state = not current_state - - instance_id = index.data(Roles.ObjectIdRole) - instance_item = self.instance_model.instance_items[instance_id] - instance_item.setData(state, QtCore.Qt.CheckStateRole) - - self.controller.instance_toggled.emit( - instance_item.instance, current_state, state - ) - - self.update_compatibility() - - def on_instance_group_created(self, index): - _index = self.instance_sort_proxy.mapFromSource(index) - self.overview_instance_view.expand(_index) - - def on_plugin_toggle(self, index, state=None): - """An item is requesting to be toggled""" - if not index.data(Roles.IsOptionalRole): - return self.info("This item is mandatory") - - if self.controller.collect_state != 1: - return self.info("Cannot toggle") - - if state is None: - state = not index.data(QtCore.Qt.CheckStateRole) - - plugin_id = index.data(Roles.ObjectIdRole) - plugin_item = self.plugin_model.plugin_items[plugin_id] - plugin_item.setData(state, QtCore.Qt.CheckStateRole) - - self.update_compatibility() - - def on_tab_changed(self, target): - previous_page = None - target_page = None - direction = None - for name, page in self.pages: - if name == target: - target_page = page - if direction is None: - direction = -1 - elif name == self._current_page: - previous_page = page - if direction is None: - direction = 1 - else: - page.setVisible(False) - - self._current_page = target - self.slide_page(previous_page, target_page, direction) - - def slide_page(self, previous_page, target_page, direction): - if previous_page is None: - for name, page in self.pages: - for _name, _page in self.pages: - if name != _name: - _page.hide() - page.show() - page.hide() - - if ( - previous_page == target_page - or previous_page is None - ): - if not target_page.isVisible(): - target_page.show() - return - - if not settings.Animated: - previous_page.setVisible(False) - target_page.setVisible(True) - return - - width = previous_page.frameGeometry().width() - offset = QtCore.QPoint(direction * width, 0) - - previous_rect = ( - previous_page.frameGeometry().x(), - previous_page.frameGeometry().y(), - width, - previous_page.frameGeometry().height() - ) - curr_pos = previous_page.pos() - - previous_page.hide() - target_page.show() - target_page.update() - target_rect = ( - target_page.frameGeometry().x(), - target_page.frameGeometry().y(), - target_page.frameGeometry().width(), - target_page.frameGeometry().height() - ) - previous_page.show() - - target_page.raise_() - previous_page.setGeometry(*previous_rect) - target_page.setGeometry(*target_rect) - - target_page.move(curr_pos + offset) - - duration = 250 - - anim_old = QtCore.QPropertyAnimation( - previous_page, b"pos", self - ) - anim_old.setDuration(duration) - anim_old.setStartValue(curr_pos) - anim_old.setEndValue(curr_pos - offset) - anim_old.setEasingCurve(QtCore.QEasingCurve.OutQuad) - - anim_new = QtCore.QPropertyAnimation( - target_page, b"pos", self - ) - anim_new.setDuration(duration) - anim_new.setStartValue(curr_pos + offset) - anim_new.setEndValue(curr_pos) - anim_new.setEasingCurve(QtCore.QEasingCurve.OutQuad) - - anim_group = QtCore.QParallelAnimationGroup(self) - anim_group.addAnimation(anim_old) - anim_group.addAnimation(anim_new) - - def slide_finished(): - previous_page.hide() - self.footer_items_visibility() - - anim_group.finished.connect(slide_finished) - anim_group.start() - - def footer_items_visibility( - self, - comment_visible=None, - terminal_filters_visibile=None - ): - target = self._current_page - comment_visibility = ( - not self.perspective_widget.isVisible() - and not target == "terminal" - and self.comment_box.isEnabled() - ) - terminal_filters_visibility = ( - target == "terminal" - or self.perspective_widget.isVisible() - ) - - if comment_visible is not None and comment_visibility: - comment_visibility = comment_visible - - if ( - terminal_filters_visibile is not None - and terminal_filters_visibility - ): - terminal_filters_visibility = terminal_filters_visibile - - duration = 150 - - hiding_widgets = [] - showing_widgets = [] - if (comment_visibility != ( - self.comment_main_widget.isVisible() - )): - if self.comment_main_widget.isVisible(): - hiding_widgets.append(self.comment_main_widget) - else: - showing_widgets.append(self.comment_main_widget) - - if (terminal_filters_visibility != ( - self.terminal_filters_widget.isVisible() - )): - if self.terminal_filters_widget.isVisible(): - hiding_widgets.append(self.terminal_filters_widget) - else: - showing_widgets.append(self.terminal_filters_widget) - - if not hiding_widgets and not showing_widgets: - return - - hiding_widgets_queue = Queue() - showing_widgets_queue = Queue() - widgets_by_pos_y = {} - for widget in hiding_widgets: - key = widget.mapToGlobal(widget.rect().topLeft()).x() - widgets_by_pos_y[key] = widget - - for key in sorted(widgets_by_pos_y.keys()): - widget = widgets_by_pos_y[key] - hiding_widgets_queue.put((widget, )) - - for widget in hiding_widgets: - widget.hide() - - for widget in showing_widgets: - widget.show() - - self.footer_widget.updateGeometry() - widgets_by_pos_y = {} - for widget in showing_widgets: - key = widget.mapToGlobal(widget.rect().topLeft()).x() - widgets_by_pos_y[key] = widget - - for key in reversed(sorted(widgets_by_pos_y.keys())): - widget = widgets_by_pos_y[key] - showing_widgets_queue.put(widget) - - for widget in showing_widgets: - widget.hide() - - for widget in hiding_widgets: - widget.show() - - def process_showing(): - if showing_widgets_queue.empty(): - return - - widget = showing_widgets_queue.get() - widget.show() - - widget_rect = widget.frameGeometry() - second_rect = QtCore.QRect(widget_rect) - second_rect.setTopLeft(second_rect.bottomLeft()) - - animation = QtCore.QPropertyAnimation( - widget, b"geometry", self - ) - animation.setDuration(duration) - animation.setStartValue(second_rect) - animation.setEndValue(widget_rect) - animation.setEasingCurve(QtCore.QEasingCurve.OutQuad) - - animation.finished.connect(process_showing) - animation.start() - - def process_hiding(): - if hiding_widgets_queue.empty(): - return process_showing() - - item = hiding_widgets_queue.get() - if isinstance(item, tuple): - widget = item[0] - hiding_widgets_queue.put(widget) - widget_rect = widget.frameGeometry() - second_rect = QtCore.QRect(widget_rect) - second_rect.setTopLeft(second_rect.bottomLeft()) - - anim = QtCore.QPropertyAnimation( - widget, b"geometry", self - ) - anim.setDuration(duration) - anim.setStartValue(widget_rect) - anim.setEndValue(second_rect) - anim.setEasingCurve(QtCore.QEasingCurve.OutQuad) - - anim.finished.connect(process_hiding) - anim.start() - else: - item.hide() - return process_hiding() - - process_hiding() - - def on_validate_clicked(self): - self.comment_box.setEnabled(False) - self.footer_items_visibility() - self.intent_box.setEnabled(False) - - self._add_intent_to_context() - - self.validate() - - def on_play_clicked(self): - self.comment_box.setEnabled(False) - self.footer_items_visibility() - self.intent_box.setEnabled(False) - - self._add_intent_to_context() - - self.publish() - - def on_reset_clicked(self): - self.reset() - - def on_stop_clicked(self): - self.info("Stopping..") - self.controller.stop() - - # TODO checks - self.footer_button_reset.setEnabled(True) - self.footer_button_play.setEnabled(False) - self.footer_button_stop.setEnabled(False) - - def on_suspend_clicked(self, value=None): - self.apply_log_suspend_value(not self._suspend_logs) - - def apply_log_suspend_value(self, value): - self._suspend_logs = value - if self._current_page == "terminal": - self.tabs["overview"].setChecked(True) - - self.tabs["terminal"].setVisible(not self._suspend_logs) - - def on_comment_entered(self): - """The user has typed a comment.""" - self.controller.context.data["comment"] = self.comment_box.text() - - def on_about_to_process(self, plugin, instance): - """Reflect currently running pair in GUI""" - if instance is None: - instance_id = self.controller.context.id - else: - instance_id = instance.id - - instance_item = ( - self.instance_model.instance_items[instance_id] - ) - instance_item.setData( - {InstanceStates.InProgress: True}, - Roles.PublishFlagsRole - ) - - plugin_item = self.plugin_model.plugin_items[plugin._id] - plugin_item.setData( - {PluginStates.InProgress: True}, - Roles.PublishFlagsRole - ) - - self.info("{} {}".format( - self.tr("Processing"), plugin_item.data(QtCore.Qt.DisplayRole) - )) - - visibility = True - if hasattr(plugin, "hide_ui_on_process") and plugin.hide_ui_on_process: - visibility = False - self._hidden_for_plugin_process = not visibility - - self._ensure_visible(visibility) - - def _ensure_visible(self, visible): - if self.isVisible() == visible: - return - - if not visible: - self.setVisible(visible) - else: - self.show() - self.raise_() - self.activateWindow() - self.showNormal() - - def on_plugin_action_menu_requested(self, pos): - """The user right-clicked on a plug-in - __________ - | | - | Action 1 | - | Action 2 | - | Action 3 | - | | - |__________| - - """ - - index = self.overview_plugin_view.indexAt(pos) - actions = index.data(Roles.PluginValidActionsRole) - - if not actions: - return - - menu = QtWidgets.QMenu(self) - plugin_id = index.data(Roles.ObjectIdRole) - plugin_item = self.plugin_model.plugin_items[plugin_id] - print("plugin is: %s" % plugin_item.plugin) - - for action in actions: - qaction = QtWidgets.QAction(action.label or action.__name__, self) - qaction.triggered.connect(partial(self.act, plugin_item, action)) - menu.addAction(qaction) - - menu.popup(self.overview_plugin_view.viewport().mapToGlobal(pos)) - - def update_compatibility(self): - self.plugin_model.update_compatibility() - self.plugin_proxy.invalidateFilter() - - def on_was_reset(self): - # Append context object to instances model - self.instance_model.append(self.controller.context) - - for plugin in self.controller.plugins: - self.plugin_model.append(plugin) - - self.overview_instance_view.expandAll() - self.overview_plugin_view.expandAll() - - self.presets_button.clearMenu() - if self.controller.possible_presets: - self.presets_button.setEnabled(True) - for key in self.controller.possible_presets: - self.presets_button.addItem( - key, partial(self.set_presets, key) - ) - - self.instance_model.restore_checkstates() - self.plugin_model.restore_checkstates() - - self.perspective_widget.reset() - - # Append placeholder comment from Context - # This allows users to inject a comment from elsewhere, - # or to perhaps provide a placeholder comment/template - # for artists to fill in. - comment = self.controller.context.data.get("comment") - self.comment_box.setText(comment or None) - self.comment_box.setEnabled(True) - self.footer_items_visibility() - - self.intent_box.setEnabled(True) - - # Refresh tab - self.on_tab_changed(self._current_page) - self.update_compatibility() - - self.button_suspend_logs.setEnabled(False) - - self.footer_button_validate.setEnabled(False) - self.footer_button_reset.setEnabled(False) - self.footer_button_stop.setEnabled(True) - self.footer_button_play.setEnabled(False) - - self._update_state() - - def on_passed_group(self, order): - for group_item in self.instance_model.group_items.values(): - group_index = self.instance_sort_proxy.mapFromSource( - group_item.index() - ) - if self.overview_instance_view.isExpanded(group_index): - continue - - if group_item.publish_states & GroupStates.HasError: - self.overview_instance_view.expand(group_index) - - for group_item in self.plugin_model.group_items.values(): - # TODO check only plugins from the group - if group_item.publish_states & GroupStates.HasFinished: - continue - - if order != group_item.order: - continue - - group_index = self.plugin_proxy.mapFromSource(group_item.index()) - if group_item.publish_states & GroupStates.HasError: - self.overview_plugin_view.expand(group_index) - continue - - group_item.setData( - {GroupStates.HasFinished: True}, - Roles.PublishFlagsRole - ) - self.overview_plugin_view.setAnimated(False) - self.overview_plugin_view.collapse(group_index) - - self._update_state() - - def on_was_stopped(self): - self.overview_plugin_view.setAnimated(settings.Animated) - errored = self.controller.errored - if self.controller.collect_state == 0: - self.footer_button_play.setEnabled(False) - self.footer_button_validate.setEnabled(False) - else: - self.footer_button_play.setEnabled(not errored) - self.footer_button_validate.setEnabled( - not errored and not self.controller.validated - ) - self.footer_button_play.setFocus() - - self.footer_button_reset.setEnabled(True) - self.footer_button_stop.setEnabled(False) - if errored: - self.footer_widget.setProperty("success", 0) - self.footer_widget.style().polish(self.footer_widget) - - suspend_log_bool = ( - self.controller.collect_state == 1 - and not self.controller.stopped - ) - self.button_suspend_logs.setEnabled(suspend_log_bool) - - self._update_state() - - if self._hidden_for_plugin_process: - self._hidden_for_plugin_process = False - self._ensure_visible(True) - - def on_was_skipped(self, plugin): - plugin_item = self.plugin_model.plugin_items[plugin.id] - plugin_item.setData( - {PluginStates.WasSkipped: True}, - Roles.PublishFlagsRole - ) - - def on_was_finished(self): - self.overview_plugin_view.setAnimated(settings.Animated) - self.footer_button_play.setEnabled(False) - self.footer_button_validate.setEnabled(False) - self.footer_button_reset.setEnabled(True) - self.footer_button_stop.setEnabled(False) - - if self.controller.errored: - success_val = 0 - self.info(self.tr("Stopped due to error(s), see Terminal.")) - self.comment_box.setEnabled(False) - self.intent_box.setEnabled(False) - - else: - success_val = 1 - self.info(self.tr("Finished successfully!")) - - self.footer_widget.setProperty("success", success_val) - self.footer_widget.style().polish(self.footer_widget) - - for instance_item in ( - self.instance_model.instance_items.values() - ): - instance_item.setData( - {InstanceStates.HasFinished: True}, - Roles.PublishFlagsRole - ) - - for group_item in self.instance_model.group_items.values(): - group_item.setData( - {GroupStates.HasFinished: True}, - Roles.PublishFlagsRole - ) - - self.update_compatibility() - self._update_state() - - def on_was_processed(self, result): - existing_ids = set(self.instance_model.instance_items.keys()) - existing_ids.remove(self.controller.context.id) - for instance in self.controller.context: - if instance.id not in existing_ids: - self.instance_model.append(instance) - else: - existing_ids.remove(instance.id) - - for instance_id in existing_ids: - self.instance_model.remove(instance_id) - - result["records"] = self.terminal_model.prepare_records( - result, - self._suspend_logs - ) - - plugin_item = self.plugin_model.update_with_result(result) - instance_item = self.instance_model.update_with_result(result) - - self.terminal_model.update_with_result(result) - - self.update_compatibility() - - if self.perspective_widget.isVisible(): - self.perspective_widget.update_context( - plugin_item, instance_item - ) - - if self._hidden_for_plugin_process: - self._hidden_for_plugin_process = False - self._ensure_visible(True) - - # ------------------------------------------------------------------------- - # - # Functions - # - # ------------------------------------------------------------------------- - - def reset(self): - """Prepare GUI for reset""" - self.info(self.tr("About to reset..")) - - self.presets_button.setEnabled(False) - self.footer_widget.setProperty("success", -1) - self.footer_widget.style().polish(self.footer_widget) - - self.instance_model.store_checkstates() - self.plugin_model.store_checkstates() - - # Reset current ids to secure no previous instances get mixed in. - self.instance_model.reset() - self.plugin_model.reset() - self.intent_model.reset() - self.terminal_model.reset() - - self.footer_button_stop.setEnabled(False) - self.footer_button_reset.setEnabled(False) - self.footer_button_validate.setEnabled(False) - self.footer_button_play.setEnabled(False) - - self.intent_box.setVisible(self.intent_model.has_items) - if self.intent_model.has_items: - self.intent_box.setCurrentIndex(self.intent_model.default_index) - - self.comment_box.placeholder.setVisible(False) - # Launch controller reset - self.controller.reset() - if not self.comment_box.text(): - self.comment_box.placeholder.setVisible(True) - - def validate(self): - self.info(self.tr("Preparing validate..")) - self.footer_button_stop.setEnabled(True) - self.footer_button_reset.setEnabled(False) - self.footer_button_validate.setEnabled(False) - self.footer_button_play.setEnabled(False) - - self.button_suspend_logs.setEnabled(False) - - self.controller.validate() - - self._update_state() - - def publish(self): - self.info(self.tr("Preparing publish..")) - self.footer_button_stop.setEnabled(True) - self.footer_button_reset.setEnabled(False) - self.footer_button_validate.setEnabled(False) - self.footer_button_play.setEnabled(False) - - self.button_suspend_logs.setEnabled(False) - - self.controller.publish() - - self._update_state() - - def act(self, plugin_item, action): - self.info("%s %s.." % (self.tr("Preparing"), action)) - - self.footer_button_stop.setEnabled(True) - self.footer_button_reset.setEnabled(False) - self.footer_button_validate.setEnabled(False) - self.footer_button_play.setEnabled(False) - - # Cause view to update, but it won't visually - # happen until Qt is given time to idle.. - plugin_item.setData( - PluginActionStates.InProgress, Roles.PluginActionProgressRole - ) - - # Give Qt time to draw - self.controller.act(plugin_item.plugin, action) - - self.info(self.tr("Action prepared.")) - - def on_was_acted(self, result): - self.footer_button_reset.setEnabled(True) - self.footer_button_stop.setEnabled(False) - - # Update action with result - plugin_item = self.plugin_model.plugin_items[result["plugin"].id] - action_state = plugin_item.data(Roles.PluginActionProgressRole) - action_state |= PluginActionStates.HasFinished - result["records"] = self.terminal_model.prepare_records( - result, - self._suspend_logs - ) - - if result.get("error"): - action_state |= PluginActionStates.HasFailed - - plugin_item.setData(action_state, Roles.PluginActionProgressRole) - - self.terminal_model.update_with_result(result) - plugin_item = self.plugin_model.update_with_result(result) - instance_item = self.instance_model.update_with_result(result) - - if self.perspective_widget.isVisible(): - self.perspective_widget.update_context( - plugin_item, instance_item - ) - - def closeEvent(self, event): - """Perform post-flight checks before closing - - Make sure processing of any kind is wrapped up before closing - - """ - - self.info(self.tr("Closing..")) - - if self.controller.is_running: - self.info(self.tr("..as soon as processing is finished..")) - self.controller.stop() - - self.info(self.tr("Cleaning up controller..")) - self.controller.cleanup() - - self.overview_instance_view.setModel(None) - self.overview_plugin_view.setModel(None) - self.terminal_view.setModel(None) - - event.accept() - - def reject(self): - """Handle ESC key""" - - if self.controller.is_running: - self.info(self.tr("Stopping..")) - self.controller.stop() - - # ------------------------------------------------------------------------- - # - # Feedback - # - # ------------------------------------------------------------------------- - - def _update_state(self): - self.footer_info.setText(self.controller.current_state) - - def info(self, message): - """Print user-facing information - - Arguments: - message (str): Text message for the user - - """ - # Include message in terminal - self.terminal_model.append([{ - "label": message, - "type": "info" - }]) - - if settings.PrintInfo: - # Print message to console - util.u_print(message) - - def warning(self, message): - """Block processing and print warning until user hits "Continue" - - Arguments: - message (str): Message to display - - """ - - # TODO(marcus): Implement this. - self.info(message) - - def heads_up(self, title, message, command=None): - """Provide a front-and-center message with optional command - - Arguments: - title (str): Bold and short message - message (str): Extended message - command (optional, callable): Function is provided as a button - - """ - - # TODO(marcus): Implement this. - self.info(message) diff --git a/client/ayon_core/tools/utils/projects_widget.py b/client/ayon_core/tools/utils/projects_widget.py index fd361493ab..88d8a6c9f5 100644 --- a/client/ayon_core/tools/utils/projects_widget.py +++ b/client/ayon_core/tools/utils/projects_widget.py @@ -286,6 +286,7 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): self._sort_by_type = True # Disable case sensitivity self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) def _type_sort(self, l_index, r_index): if not self._sort_by_type: diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index 1074b6d4fb..0cd6d68ab3 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -1,4 +1,5 @@ import logging +import math from typing import Optional, List, Set, Any from qtpy import QtWidgets, QtCore, QtGui @@ -410,10 +411,12 @@ class ExpandingTextEdit(QtWidgets.QTextEdit): document = self.document().clone() document.setTextWidth(document_width) - return margins.top() + document.size().height() + margins.bottom() + return math.ceil( + margins.top() + document.size().height() + margins.bottom() + ) def sizeHint(self): - width = super(ExpandingTextEdit, self).sizeHint().width() + width = super().sizeHint().width() return QtCore.QSize(width, self.heightForWidth(width)) diff --git a/client/ayon_core/tools/workfiles/abstract.py b/client/ayon_core/tools/workfiles/abstract.py index b78e987032..152ca33d99 100644 --- a/client/ayon_core/tools/workfiles/abstract.py +++ b/client/ayon_core/tools/workfiles/abstract.py @@ -1016,6 +1016,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): workdir, filename, template_key, + artist_note, ): """Save current state of workfile to workarea. @@ -1040,6 +1041,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): workdir, filename, template_key, + artist_note, ): """Action to copy published workfile representation to workarea. @@ -1054,12 +1056,13 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): workdir (str): Workarea directory. filename (str): Workarea filename. template_key (str): Template key. + artist_note (str): Artist note. """ pass @abstractmethod - def duplicate_workfile(self, src_filepath, workdir, filename): + def duplicate_workfile(self, src_filepath, workdir, filename, artist_note): """Duplicate workfile. Workfiles is not opened when done. @@ -1068,6 +1071,7 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon): src_filepath (str): Source workfile path. workdir (str): Destination workdir. filename (str): Destination filename. + artist_note (str): Artist note. """ pass diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index ca97015eea..3a7459da0c 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -554,6 +554,7 @@ class BaseWorkfileController( workdir, filename, template_key, + artist_note, ): self._emit_event("save_as.started") @@ -565,6 +566,7 @@ class BaseWorkfileController( workdir, filename, template_key, + artist_note=artist_note, ) except Exception: failed = True @@ -584,6 +586,7 @@ class BaseWorkfileController( workdir, filename, template_key, + artist_note, ): self._emit_event("copy_representation.started") @@ -595,6 +598,7 @@ class BaseWorkfileController( workdir, filename, template_key, + artist_note, src_filepath=representation_filepath ) except Exception: @@ -608,7 +612,7 @@ class BaseWorkfileController( {"failed": failed}, ) - def duplicate_workfile(self, src_filepath, workdir, filename): + def duplicate_workfile(self, src_filepath, workdir, filename, artist_note): self._emit_event("workfile_duplicate.started") failed = False @@ -701,11 +705,12 @@ class BaseWorkfileController( def _save_as_workfile( self, - folder_id, - task_id, - workdir, - filename, - template_key, + folder_id: str, + task_id: str, + workdir: str, + filename: str, + template_key: str, + artist_note: str, src_filepath=None, ): # Trigger before save event @@ -748,7 +753,11 @@ class BaseWorkfileController( self._host_save_workfile(dst_filepath) # Make sure workfile info exists - self.save_workfile_info(folder_id, task_name, dst_filepath, None) + if not artist_note: + artist_note = None + self.save_workfile_info( + folder_id, task_name, dst_filepath, note=artist_note + ) # Create extra folders create_workdir_extra_folders( diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget.py b/client/ayon_core/tools/workfiles/widgets/files_widget.py index dbe5966c31..f0b74f4289 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget.py @@ -213,7 +213,8 @@ class FilesWidget(QtWidgets.QWidget): self._controller.duplicate_workfile( filepath, result["workdir"], - result["filename"] + result["filename"], + artist_note=result["artist_note"] ) def _on_workarea_browse_clicked(self): @@ -261,6 +262,7 @@ class FilesWidget(QtWidgets.QWidget): result["workdir"], result["filename"], result["template_key"], + artist_note=result["artist_note"] ) def _on_workarea_path_changed(self, event): @@ -313,6 +315,7 @@ class FilesWidget(QtWidgets.QWidget): result["workdir"], result["filename"], result["template_key"], + artist_note=result["artist_note"] ) def _on_save_as_request(self): diff --git a/client/ayon_core/tools/workfiles/widgets/save_as_dialog.py b/client/ayon_core/tools/workfiles/widgets/save_as_dialog.py index 77dac1198a..bddff816fe 100644 --- a/client/ayon_core/tools/workfiles/widgets/save_as_dialog.py +++ b/client/ayon_core/tools/workfiles/widgets/save_as_dialog.py @@ -1,6 +1,6 @@ from qtpy import QtWidgets, QtCore -from ayon_core.tools.utils import PlaceholderLineEdit +from ayon_core.tools.utils import PlaceholderLineEdit, PlaceholderPlainTextEdit class SubversionLineEdit(QtWidgets.QWidget): @@ -143,6 +143,11 @@ class SaveAsDialog(QtWidgets.QDialog): version_layout.addWidget(version_input) version_layout.addWidget(last_version_check) + # Artist note widget + artist_note_input = PlaceholderPlainTextEdit(inputs_widget) + artist_note_input.setPlaceholderText( + "Provide a note about this workfile.") + # Preview widget preview_widget = QtWidgets.QLabel("Preview filename", inputs_widget) preview_widget.setWordWrap(True) @@ -161,6 +166,7 @@ class SaveAsDialog(QtWidgets.QDialog): subversion_label = QtWidgets.QLabel("Subversion:", inputs_widget) extension_label = QtWidgets.QLabel("Extension:", inputs_widget) preview_label = QtWidgets.QLabel("Preview:", inputs_widget) + artist_note_label = QtWidgets.QLabel("Artist Note:", inputs_widget) # Build inputs inputs_layout = QtWidgets.QGridLayout(inputs_widget) @@ -172,6 +178,8 @@ class SaveAsDialog(QtWidgets.QDialog): inputs_layout.addWidget(extension_combobox, 2, 1) inputs_layout.addWidget(preview_label, 3, 0) inputs_layout.addWidget(preview_widget, 3, 1) + inputs_layout.addWidget(artist_note_label, 4, 0, 1, 2) + inputs_layout.addWidget(artist_note_input, 5, 0, 1, 2) # Build layout main_layout = QtWidgets.QVBoxLayout(self) @@ -206,11 +214,13 @@ class SaveAsDialog(QtWidgets.QDialog): self._extension_combobox = extension_combobox self._subversion_input = subversion_input self._preview_widget = preview_widget + self._artist_note_input = artist_note_input self._version_label = version_label self._subversion_label = subversion_label self._extension_label = extension_label self._preview_label = preview_label + self._artist_note_label = artist_note_label # Post init setup @@ -322,6 +332,7 @@ class SaveAsDialog(QtWidgets.QDialog): "folder_id": self._folder_id, "task_id": self._task_id, "template_key": self._template_key, + "artist_note": self._artist_note_input.toPlainText(), } self.close() diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index e533e08fe4..d021a03e7e 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.1.3+dev" +__version__ = "1.1.6+dev" diff --git a/docs/css/custom.css b/docs/css/custom.css new file mode 100644 index 0000000000..28461e6eca --- /dev/null +++ b/docs/css/custom.css @@ -0,0 +1,12 @@ +[data-md-color-scheme="slate"] { + /* simple slate overrides */ + --md-primary-fg-color: hsl(155, 49%, 50%); + --md-accent-fg-color: rgb(93, 200, 156); + --md-typeset-a-color: hsl(155, 49%, 45%) !important; +} +[data-md-color-scheme="default"] { + /* simple default overrides */ + --md-primary-fg-color: hsl(155, 49%, 50%); + --md-accent-fg-color: rgb(93, 200, 156); + --md-typeset-a-color: hsl(155, 49%, 45%) !important; +} diff --git a/docs/img/ay-symbol-blackw-full.png b/docs/img/ay-symbol-blackw-full.png new file mode 100644 index 0000000000..5edda784cc Binary files /dev/null and b/docs/img/ay-symbol-blackw-full.png differ diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico new file mode 100644 index 0000000000..62b656e501 Binary files /dev/null and b/docs/img/favicon.ico differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..612c7a5e0d --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +--8<-- "README.md" diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000000..f409d45232 --- /dev/null +++ b/docs/license.md @@ -0,0 +1 @@ +--8<-- "LICENSE" diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..8e4c2663bc --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,71 @@ +site_name: ayon-core +repo_url: https://github.com/ynput/ayon-core + +nav: + - Home: index.md + - License: license.md + +theme: + name: material + palette: + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/toggle-switch-off-outline + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/toggle-switch + name: Switch to dark mode + logo: img/ay-symbol-blackw-full.png + favicon: img/favicon.ico + features: + - navigation.sections + - navigation.path + - navigation.prune + +extra: + version: + provider: mike + +extra_css: [css/custom.css] + +markdown_extensions: + - mdx_gh_links + - pymdownx.snippets + +plugins: + - search + - offline + - mkdocs-autoapi: + autoapi_dir: ./ + autoapi_add_nav_entry: Reference + autoapi_ignore: + - .* + - docs/**/* + - tests/**/* + - tools/**/* + - stubs/**/* # mocha fix + - ./**/pythonrc.py # houdini fix + - .*/**/* + - ./*.py + - mkdocstrings: + handlers: + python: + paths: + - ./ + - client/* + - server/* + - services/* + - minify: + minify_html: true + minify_js: true + minify_css: true + htmlmin_opts: + remove_comments: true + cache_safe: true + - mike + +hooks: + - mkdocs_hooks.py diff --git a/mkdocs_hooks.py b/mkdocs_hooks.py new file mode 100644 index 0000000000..1faa1954f9 --- /dev/null +++ b/mkdocs_hooks.py @@ -0,0 +1,191 @@ +import os +from pathlib import Path +from shutil import rmtree +import json +import glob +import logging + +TMP_FILE = "./missing_init_files.json" +NFILES = [] + +# ----------------------------------------------------------------------------- + + +class ColorFormatter(logging.Formatter): + grey = "\x1b[38;20m" + green = "\x1b[32;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + fmt = ( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s " # noqa + "(%(filename)s:%(lineno)d)" + ) + + FORMATS = { + logging.DEBUG: grey + fmt + reset, + logging.INFO: green + fmt + reset, + logging.WARNING: yellow + fmt + reset, + logging.ERROR: red + fmt + reset, + logging.CRITICAL: bold_red + fmt + reset, + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + +ch = logging.StreamHandler() +ch.setFormatter(ColorFormatter()) + +logging.basicConfig( + level=logging.INFO, + handlers=[ch], +) + + +# ----------------------------------------------------------------------------- + + +def create_init_file(dirpath, msg): + global NFILES + ini_file = f"{dirpath}/__init__.py" + Path(ini_file).touch() + NFILES.append(ini_file) + logging.info(f"{msg}: created '{ini_file}'") + + +def create_parent_init_files(dirpath: str, rootpath: str, msg: str): + parent_path = dirpath + while parent_path != rootpath: + parent_path = os.path.dirname(parent_path) + parent_init = os.path.join(parent_path, "__init__.py") + if not os.path.exists(parent_init): + create_init_file(parent_path, msg) + else: + break + + +def add_missing_init_files(*roots, msg=""): + """ + This function takes in one or more root directories as arguments and scans + them for Python files without an `__init__.py` file. It generates a JSON + file named `missing_init_files.json` containing the paths of these files. + + Args: + *roots: Variable number of root directories to scan. + + Returns: + None + """ + + for root in roots: + if not os.path.exists(root): + continue + rootpath = os.path.abspath(root) + for dirpath, dirs, files in os.walk(rootpath): + if "__init__.py" in files: + continue + + if "." in dirpath: + continue + + if not glob.glob(os.path.join(dirpath, "*.py")): + continue + + create_init_file(dirpath, msg) + create_parent_init_files(dirpath, rootpath, msg) + + with open(TMP_FILE, "w") as f: + json.dump(NFILES, f) + + +def remove_missing_init_files(msg=""): + """ + This function removes temporary `__init__.py` files created in the + `add_missing_init_files()` function. It reads the paths of these files from + a JSON file named `missing_init_files.json`. + + Args: + None + + Returns: + None + """ + global NFILES + nfiles = [] + if os.path.exists(TMP_FILE): + with open(TMP_FILE, "r") as f: + nfiles = json.load(f) + else: + nfiles = NFILES + + for file in nfiles: + Path(file).unlink() + logging.info(f"{msg}: removed {file}") + + os.remove(TMP_FILE) + NFILES = [] + + +def remove_pychache_dirs(msg=""): + """ + This function walks the current directory and removes all existing + '__pycache__' directories. + + Args: + msg: An optional message to display during the removal process. + + Returns: + None + """ + nremoved = 0 + + for dirpath, dirs, files in os.walk("."): + if "__pycache__" in dirs: + pydir = Path(f"{dirpath}/__pycache__") + rmtree(pydir) + nremoved += 1 + logging.info(f"{msg}: removed '{pydir}'") + + if not nremoved: + logging.info(f"{msg}: no __pycache__ dirs found") + + +# mkdocs hooks ---------------------------------------------------------------- + + +def on_startup(command, dirty): + remove_pychache_dirs(msg="HOOK - on_startup") + + +def on_pre_build(config): + """ + This function is called before the MkDocs build process begins. It adds + temporary `__init__.py` files to directories that do not contain one, to + make sure mkdocs doesn't ignore them. + """ + try: + add_missing_init_files( + "client", + "server", + "services", + msg="HOOK - on_pre_build", + ) + except BaseException as e: + logging.error(e) + remove_missing_init_files( + msg="HOOK - on_post_build: cleaning up on error !" + ) + raise + + +def on_post_build(config): + """ + This function is called after the MkDocs build process ends. It removes + temporary `__init__.py` files that were added in the `on_pre_build()` + function. + """ + remove_missing_init_files(msg="HOOK - on_post_build") diff --git a/package.py b/package.py index 02e2f25384..9af45719a7 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.1.3+dev" +version = "1.1.6+dev" client_dir = "ayon_core" diff --git a/poetry.lock b/poetry.lock index 2d040a5f91..96e1dc0f4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,6 +49,40 @@ appdirs = ">=1,<2" requests = ">=2.27.1" Unidecode = ">=1.3.0" +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "backrefs" +version = "5.8" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"}, + {file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"}, + {file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"}, + {file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"}, + {file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"}, + {file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"}, +] + +[package.extras] +extras = ["regex"] + [[package]] name = "certifi" version = "2025.1.31" @@ -175,6 +209,21 @@ files = [ {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "clique" version = "2.0.0" @@ -217,12 +266,22 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "csscompressor" +version = "0.9.5" +description = "A python port of YUI CSS Compressor" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05"}, +] + [[package]] name = "distlib" version = "0.3.9" @@ -267,6 +326,50 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3) testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "1.6.2" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "griffe-1.6.2-py3-none-any.whl", hash = "sha256:6399f7e663150e4278a312a8e8a14d2f3d7bd86e2ef2f8056a1058e38579c2ee"}, + {file = "griffe-1.6.2.tar.gz", hash = "sha256:3a46fa7bd83280909b63c12b9a975732a927dd97809efe5b7972290b606c5d91"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "htmlmin2" +version = "0.1.13" +description = "An HTML Minifier" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2"}, +] + [[package]] name = "identify" version = "2.6.7" @@ -297,6 +400,53 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "importlib-metadata" +version = "8.6.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, + {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -309,6 +459,405 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsmin" +version = "3.0.1" +description = "JavaScript minifier." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"}, +] + +[[package]] +name = "markdown" +version = "3.7" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-checklist" +version = "0.4.4" +description = "Python Markdown extension for task lists with checkboxes" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "markdown-checklist-0.4.4.tar.gz", hash = "sha256:69c93850798b1e01cdc6fcd4a80592d941f669f6451bbf69c71a4ffd1142f849"}, +] + +[package.dependencies] +markdown = "*" + +[package.extras] +coverage = ["coverage", "figleaf"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdx-gh-links" +version = "0.4" +description = "An extension to Python-Markdown which adds support for shorthand links to GitHub users, repositories, issues and commits." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "mdx_gh_links-0.4-py3-none-any.whl", hash = "sha256:9057bca1fa5280bf1fcbf354381e46c9261cc32c2d5c0407801f8a910be5f099"}, + {file = "mdx_gh_links-0.4.tar.gz", hash = "sha256:41d5aac2ab201425aa0a19373c4095b79e5e015fdacfe83c398199fe55ca3686"}, +] + +[package.dependencies] +markdown = ">=3.0.0" + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mike" +version = "2.1.3" +description = "Manage multiple versions of your MkDocs-powered documentation" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "mike-2.1.3-py3-none-any.whl", hash = "sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a"}, + {file = "mike-2.1.3.tar.gz", hash = "sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810"}, +] + +[package.dependencies] +importlib-metadata = "*" +importlib-resources = "*" +jinja2 = ">=2.7" +mkdocs = ">=1.0" +pyparsing = ">=3.0" +pyyaml = ">=5.1" +pyyaml-env-tag = "*" +verspec = "*" + +[package.extras] +dev = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] +test = ["coverage", "flake8 (>=3.0)", "flake8-quotes", "shtab"] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autoapi" +version = "0.4.0" +description = "MkDocs plugin providing automatic API reference generation" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mkdocs_autoapi-0.4.0-py3-none-any.whl", hash = "sha256:ce2b5e8b4d1df37bd1273a6bce1dded152107e2280cedbfa085cea10edf8531b"}, + {file = "mkdocs_autoapi-0.4.0.tar.gz", hash = "sha256:409c2da8eb297ef51381b066744ac1cdf846220bcc779bdba680fbc5a080df1e"}, +] + +[package.dependencies] +mkdocs = ">=1.4.0" +mkdocstrings = ">=0.19.0" + +[package.extras] +python = ["mkdocstrings[python] (>=0.19.0)"] +python-legacy = ["mkdocstrings[python-legacy] (>=0.19.0)"] +vba = ["mkdocstrings-vba (>=0.0.10)"] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.1" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f"}, + {file = "mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079"}, +] + +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.6.9" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1"}, + {file = "mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocs-minify-plugin" +version = "0.8.0" +description = "An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d"}, + {file = "mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6"}, +] + +[package.dependencies] +csscompressor = ">=0.9.5" +htmlmin2 = ">=0.1.13" +jsmin = ">=3.0.1" +mkdocs = ">=1.4.1" + +[[package]] +name = "mkdocstrings" +version = "0.29.0" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d"}, + {file = "mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.11.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.6" +mkdocs-autorefs = ">=1.4" +pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=1.16.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.16.8" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocstrings_python-1.16.8-py3-none-any.whl", hash = "sha256:211b7aaf776cd45578ecb531e5ad0d3a35a8be9101a6bfa10de38a69af9d8fd8"}, + {file = "mkdocstrings_python-1.16.8.tar.gz", hash = "sha256:9453ccae69be103810c1cf6435ce71c8f714ae37fef4d87d16aa92a7c800fe1d"}, +] + +[package.dependencies] +griffe = ">=1.6.2" +mkdocs-autorefs = ">=1.4" +mkdocstrings = ">=0.28.3" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mkdocstrings-shell" +version = "1.0.3" +description = "A shell scripts/libraries handler for mkdocstrings." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocstrings_shell-1.0.3-py3-none-any.whl", hash = "sha256:b23ebe43d06c9c19a541548f34d42ee4e4324ae06423eba8a9136e295c67f345"}, + {file = "mkdocstrings_shell-1.0.3.tar.gz", hash = "sha256:3bdea6a1e794a5d0e15d461f33b92e0b9f3b9a1e2c33671d9a2b7d83c761096a"}, +] + +[package.dependencies] +mkdocstrings = ">=0.28.3" +shellman = ">=1.0.2" + [[package]] name = "mock" version = "5.1.0" @@ -400,6 +949,34 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -464,6 +1041,55 @@ files = [ {file = "pyblish_base-1.8.12-py2.py3-none-any.whl", hash = "sha256:2cbe956bfbd4175a2d7d22b344cd345800f4d4437153434ab658fc12646a11e8"}, ] +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pymdown-extensions" +version = "10.14.3" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, + {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.19.1)"] + +[[package]] +name = "pyparsing" +version = "3.2.3" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pytest" version = "8.3.4" @@ -505,6 +1131,21 @@ pytest = ">=8.3.2" [package.extras] test = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "pytest-mock (>=3.14)"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pyyaml" version = "6.0.2" @@ -568,6 +1209,21 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + [[package]] name = "requests" version = "2.32.3" @@ -629,6 +1285,35 @@ files = [ {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, ] +[[package]] +name = "shellman" +version = "1.0.2" +description = "Write documentation in comments and render it with templates." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "shellman-1.0.2-py3-none-any.whl", hash = "sha256:f8c960fd2d3785e195f86fcd8f110a8d51a950e759d82c14a5af0bd71b918b3c"}, + {file = "shellman-1.0.2.tar.gz", hash = "sha256:48cba79d6415c0d013ad4dfd2205ed81b0e468795d1886dcda943ac78eaffd38"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +jinja2 = ">=3" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + [[package]] name = "tomli" version = "2.2.1" @@ -671,6 +1356,30 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + [[package]] name = "unidecode" version = "1.3.8" @@ -701,6 +1410,21 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "verspec" +version = "0.1.0" +description = "Flexible version handling" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, + {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, +] + +[package.extras] +test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] + [[package]] name = "virtualenv" version = "20.29.2" @@ -722,7 +1446,70 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "zipp" +version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] lock-version = "2.1" python-versions = ">=3.9.1,<3.10" -content-hash = "0a399d239c49db714c1166c20286fdd5cd62faf12e45ab85833c4d6ea7a04a2a" +content-hash = "24b6215b9c20a4f64f844d3deb121618aef510b1c5ee54242e50305db6c0c4f4" diff --git a/pyproject.toml b/pyproject.toml index f065ca0c39..3a76b25c0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.1.3+dev" +version = "1.1.6+dev" description = "" authors = ["Ynput Team "] readme = "README.md" @@ -29,6 +29,17 @@ attrs = "^25.0.0" pyblish-base = "^1.8.7" clique = "^2.0.0" opentimelineio = "^0.17.0" +tomlkit = "^0.13.2" +requests = "^2.32.3" +mkdocs-material = "^9.6.7" +mkdocs-autoapi = "^0.4.0" +mkdocstrings-python = "^1.16.2" +mkdocs-minify-plugin = "^0.8.0" +markdown-checklist = "^0.4.4" +mdx-gh-links = "^0.4" +pymdown-extensions = "^10.14.3" +mike = "^2.1.3" +mkdocstrings-shell = "^1.0.2" [tool.ruff] diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index c9c66e65d9..39a9c028f9 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -12,6 +12,10 @@ from ayon_server.settings import ( from ayon_server.types import ColorRGBA_uint8 +class EnabledModel(BaseSettingsModel): + enabled: bool = SettingsField(True) + + class ValidateBaseModel(BaseSettingsModel): _isGroup = True enabled: bool = SettingsField(True) @@ -1026,6 +1030,17 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=IntegrateHeroVersionModel, title="Integrate Hero Version" ) + AttachReviewables: EnabledModel = SettingsField( + default_factory=EnabledModel, + title="Attach Reviewables", + description=( + "When enabled, expose an 'Attach Reviewables' attribute on review" + " and render instances in the publisher to allow including the" + " media to be attached to another instance.\n\n" + "If a reviewable is attached to another instance it will not be " + "published as a render/review product of its own." + ) + ) CleanUp: CleanUpModel = SettingsField( default_factory=CleanUpModel, title="Clean Up" @@ -1410,6 +1425,9 @@ DEFAULT_PUBLISH_VALUES = { ], "use_hardlinks": False }, + "AttachReviewables": { + "enabled": True, + }, "CleanUp": { "paterns": [], # codespell:ignore paterns "remove_temp_renders": False