From 0e96071996c20199e028df715d6fc9005d100991 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:20:58 +0100 Subject: [PATCH 001/258] Add shotgrid module --- .editorconfig | 19 ++ .pre-commit-config.yaml | 26 +++ mypy.ini | 5 + .../plugins/publish/submit_maya_deadline.py | 1 + .../plugins/publish/submit_publish_job.py | 1 + openpype/modules/shotgrid/README.md | 19 ++ openpype/modules/shotgrid/__init__.py | 5 + openpype/modules/shotgrid/aop/patch.py | 50 +++++ .../shotgrid/hooks/post_shotgrid_changes.py | 9 + openpype/modules/shotgrid/lib/const.py | 1 + openpype/modules/shotgrid/lib/credentials.py | 125 +++++++++++ openpype/modules/shotgrid/lib/record.py | 18 ++ openpype/modules/shotgrid/lib/server.py | 27 +++ openpype/modules/shotgrid/lib/settings.py | 39 ++++ .../publish/collect_shotgrid_entities.py | 103 +++++++++ .../publish/collect_shotgrid_session.py | 132 ++++++++++++ .../publish/integrate_shotgrid_publish.py | 77 +++++++ .../publish/integrate_shotgrid_version.py | 92 ++++++++ .../plugins/publish/validate_shotgrid_user.py | 36 ++++ openpype/modules/shotgrid/server/README.md | 5 + openpype/modules/shotgrid/shotgrid_module.py | 62 ++++++ .../tests/shotgrid/lib/test_credentials.py | 34 +++ .../shotgrid/tray/credential_dialog.py | 202 ++++++++++++++++++ .../modules/shotgrid/tray/shotgrid_tray.py | 76 +++++++ openpype/resources/app_icons/shotgrid.png | Bin 0 -> 45744 bytes .../defaults/project_settings/shotgrid.json | 22 ++ .../defaults/system_settings/modules.json | 9 +- openpype/settings/entities/__init__.py | 2 + openpype/settings/entities/enum_entity.py | 116 ++++++---- .../schemas/projects_schema/schema_main.json | 4 + .../schema_project_shotgrid.json | 98 +++++++++ .../schemas/schema_representation_tags.json | 3 + .../schemas/system_schema/schema_modules.json | 54 +++++ pyproject.toml | 1 + 34 files changed, 1433 insertions(+), 40 deletions(-) create mode 100644 .editorconfig create mode 100644 .pre-commit-config.yaml create mode 100644 mypy.ini create mode 100644 openpype/modules/shotgrid/README.md create mode 100644 openpype/modules/shotgrid/__init__.py create mode 100644 openpype/modules/shotgrid/aop/patch.py create mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py create mode 100644 openpype/modules/shotgrid/lib/const.py create mode 100644 openpype/modules/shotgrid/lib/credentials.py create mode 100644 openpype/modules/shotgrid/lib/record.py create mode 100644 openpype/modules/shotgrid/lib/server.py create mode 100644 openpype/modules/shotgrid/lib/settings.py create mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py create mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py create mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py create mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py create mode 100644 openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py create mode 100644 openpype/modules/shotgrid/server/README.md create mode 100644 openpype/modules/shotgrid/shotgrid_module.py create mode 100644 openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py create mode 100644 openpype/modules/shotgrid/tray/credential_dialog.py create mode 100644 openpype/modules/shotgrid/tray/shotgrid_tray.py create mode 100644 openpype/resources/app_icons/shotgrid.png create mode 100644 openpype/settings/defaults/project_settings/shotgrid.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..aeb5534872 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,py}] +charset = utf-8 + +[*.py] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..6a986c7dd9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/ambv/black + rev: 21.4b0 + hooks: + - id: black + language_version: "3" + args: + - "--config" + - "./pyproject.toml" +- repo: https://github.com/pycqa/flake8 + rev: "3.9.2" # pick a git hash / tag to point to + hooks: + - id: flake8 +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.902' + hooks: + - id: mypy + args: [--no-strict-optional, --ignore-missing-imports] + additional_dependencies: [tokenize-rt==3.2.0] diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000..90cde26676 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +python_version = 3.7 +ignore_missing_imports = false +check_untyped_defs = true +follow_imports = silent diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 34147712bc..f59fd3af1c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -476,6 +476,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "OPENPYPE_SG_USER", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 3c4e0d2913..e1e1ea6b94 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -113,6 +113,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "celaction": [r".*"]} enviro_filter = [ + "OPENPYPE_SG_USER", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", diff --git a/openpype/modules/shotgrid/README.md b/openpype/modules/shotgrid/README.md new file mode 100644 index 0000000000..cbee0e9bf4 --- /dev/null +++ b/openpype/modules/shotgrid/README.md @@ -0,0 +1,19 @@ +## Shotgrid Module + +### Pre-requisites + +Install and launch a [shotgrid leecher](https://github.com/Ellipsanime/shotgrid-leecher) server + +### Quickstart + +The goal of this tutorial is to synchronize an already existing shotgrid project with OpenPype. + +- Activate the shotgrid module in the **system settings** and inform the shotgrid leecher server API url + +- Create a new OpenPype project with the **project manager** + +- Inform the shotgrid authentication infos (url, script name, api key) and the shotgrid project ID related to this OpenPype project in the **project settings** + +- Use the batch interface (Tray > shotgrid > Launch batch), select your project and click "batch" + +- You can now access your shotgrid entities within the **avalon launcher** and publish informations to shotgrid with **pyblish** diff --git a/openpype/modules/shotgrid/__init__.py b/openpype/modules/shotgrid/__init__.py new file mode 100644 index 0000000000..f1337a9492 --- /dev/null +++ b/openpype/modules/shotgrid/__init__.py @@ -0,0 +1,5 @@ +from .shotgrid_module import ( + ShotgridModule, +) + +__all__ = ("ShotgridModule",) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py new file mode 100644 index 0000000000..6ca81033e2 --- /dev/null +++ b/openpype/modules/shotgrid/aop/patch.py @@ -0,0 +1,50 @@ +from copy import copy +from typing import Any, Iterator, Dict, Set + +from avalon.api import AvalonMongoDB + +from openpype.api import Logger +from openpype.modules.shotgrid.lib import ( + credentials, + settings, + server, +) + +_LOG = Logger().get_logger("ShotgridModule.patch") + + +def _patched_projects( + self: Any, projection: Any = None, only_active: bool = True +) -> Iterator[Dict[str, Any]]: + all_projects = list(self._prev_projects(projection, only_active)) + if ( + not credentials.get_local_login() + or not settings.filter_projects_by_login() + ): + return all_projects + try: + linked_names = _fetch_linked_project_names() or set() + return [x for x in all_projects if _upper(x["name"]) in linked_names] + except Exception as e: + print(e) + return all_projects + + +def _upper(x: Any) -> str: + return str(x).strip().upper() + + +def _fetch_linked_project_names() -> Set[str]: + return { + _upper(x["project_name"]) + for x in server.find_linked_projects(credentials.get_local_login()) + } + + +def patch_avalon_db() -> None: + _LOG.debug("Run avalon patching") + if AvalonMongoDB.projects is _patched_projects: + return None + _LOG.debug("Patch Avalon.projects method") + AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) + AvalonMongoDB.projects = _patched_projects diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py new file mode 100644 index 0000000000..e8369ad3cb --- /dev/null +++ b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py @@ -0,0 +1,9 @@ +from openpype.lib import PostLaunchHook + + +class PostShotgridHook(PostLaunchHook): + order = None + + def execute(self, *args, **kwargs): + print(args, kwargs) + pass diff --git a/openpype/modules/shotgrid/lib/const.py b/openpype/modules/shotgrid/lib/const.py new file mode 100644 index 0000000000..2a34800fac --- /dev/null +++ b/openpype/modules/shotgrid/lib/const.py @@ -0,0 +1 @@ +MODULE_NAME = "shotgrid" diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py new file mode 100644 index 0000000000..a334968cda --- /dev/null +++ b/openpype/modules/shotgrid/lib/credentials.py @@ -0,0 +1,125 @@ +from typing import Tuple, Optional +from urllib.parse import urlparse + +import shotgun_api3 +from shotgun_api3.shotgun import AuthenticationFault + +from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry +from openpype.modules.shotgrid.lib.record import Credentials + + +def _get_shotgrid_secure_key(hostname: str, key: str) -> str: + """Secure item key for entered hostname.""" + return f"shotgrid/{hostname}/{key}" + + +def _get_secure_value_and_registry( + hostname: str, + name: str, +) -> Tuple[str, OpenPypeSecureRegistry]: + key = _get_shotgrid_secure_key(hostname, name) + registry = OpenPypeSecureRegistry(key) + return registry.get_item(name, None), registry + + +def get_shotgrid_hostname(shotgrid_url: str) -> str: + + if not shotgrid_url: + raise Exception("Shotgrid url cannot be a null") + valid_shotgrid_url = ( + f"//{shotgrid_url}" if "//" not in shotgrid_url else shotgrid_url + ) + return urlparse(valid_shotgrid_url).hostname + + +# Credentials storing function (using keyring) + + +def get_credentials(shotgrid_url: str) -> Optional[Credentials]: + hostname = get_shotgrid_hostname(shotgrid_url) + if not hostname: + return None + login_value, _ = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + password_value, _ = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + return Credentials(login_value, password_value) + + +def save_credentials(login: str, password: str, shotgrid_url: str): + hostname = get_shotgrid_hostname(shotgrid_url) + _, login_registry = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + _, password_registry = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + clear_credentials(shotgrid_url) + login_registry.set_item(Credentials.login_key_prefix(), login) + password_registry.set_item(Credentials.password_key_prefix(), password) + + +def clear_credentials(shotgrid_url: str): + hostname = get_shotgrid_hostname(shotgrid_url) + login_value, login_registry = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + password_value, password_registry = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + + if login_value is not None: + login_registry.delete_item(Credentials.login_key_prefix()) + + if password_value is not None: + password_registry.delete_item(Credentials.password_key_prefix()) + + +# Login storing function (using json) + + +def get_local_login() -> Optional[str]: + reg = OpenPypeSettingsRegistry() + try: + return str(reg.get_item("shotgrid_login")) + except Exception: + return None + + +def save_local_login(login: str): + reg = OpenPypeSettingsRegistry() + reg.set_item("shotgrid_login", login) + + +def clear_local_login(): + reg = OpenPypeSettingsRegistry() + reg.delete_item("shotgrid_login") + + +def check_credentials( + login: str, + password: str, + shotgrid_url: str, +) -> bool: + + if not shotgrid_url or not login or not password: + return False + try: + session = shotgun_api3.Shotgun( + shotgrid_url, + login=login, + password=password, + ) + session.preferences_read() + session.close() + except AuthenticationFault: + return False + return True diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py new file mode 100644 index 0000000000..1796e6c019 --- /dev/null +++ b/openpype/modules/shotgrid/lib/record.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Credentials: + login: str + password: str + + def is_empty(self) -> bool: + return not (self.login and self.password) + + @staticmethod + def login_key_prefix() -> str: + return "login" + + @staticmethod + def password_key_prefix() -> str: + return "password" diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py new file mode 100644 index 0000000000..0fe4b8429e --- /dev/null +++ b/openpype/modules/shotgrid/lib/server.py @@ -0,0 +1,27 @@ +import traceback + +import requests +from typing import Dict, Any, List + +from openpype.api import Logger +from openpype.modules.shotgrid.lib import ( + settings as settings_lib, +) + +_LOG = Logger().get_logger("ShotgridModule.server") + + +def find_linked_projects(email: str) -> List[Dict[str, Any]]: + url = "".join( + [ + settings_lib.get_leecher_backend_url(), + "/user/", + email, + "/project-user-links", + ] + ) + try: + return requests.get(url).json() + except requests.exceptions.RequestException as e: + _LOG.error(e) + traceback.print_stack() diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py new file mode 100644 index 0000000000..0f4fc235cc --- /dev/null +++ b/openpype/modules/shotgrid/lib/settings.py @@ -0,0 +1,39 @@ +import os +from typing import Tuple, Dict, List, Any + +from pymongo import MongoClient +from openpype.api import get_system_settings, get_project_settings +from openpype.modules.shotgrid.lib.const import MODULE_NAME + + +def get_project_list() -> List[str]: + mongo_url = os.getenv("OPENPYPE_MONGO") + client = MongoClient(mongo_url) + db = client['avalon'] + return db.list_collection_names() + + +def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: + return get_project_settings(project).get(MODULE_NAME, {}) + + +def get_shotgrid_settings() -> Dict[str, Any]: + return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) + + +def get_shotgrid_servers() -> Dict[str, Any]: + return get_shotgrid_settings().get("shotgrid_settings", {}) + + +def get_leecher_backend_url() -> str: + return get_shotgrid_settings().get("leecher_backend_url") + + +def filter_projects_by_login() -> bool: + return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) + + +def get_shotgrid_event_mongo_info() -> Tuple[str, str]: + database_name = os.environ["OPENPYPE_DATABASE_NAME"] + collection_name = "shotgrid_events" + return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py new file mode 100644 index 0000000000..b89dda3a80 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -0,0 +1,103 @@ +import os +import pyblish.api +from pymongo import MongoClient +from openpype.api import get_project_settings + + +class CollectShotgridEntities(pyblish.api.ContextPlugin): + """Collect shotgrid entities according to the current context""" + + order = pyblish.api.CollectorOrder + 0.499 + label = "Shotgrid entities" + + def process(self, context): + + avalon_project = context.data.get("projectEntity") + avalon_asset = context.data.get("assetEntity") + avalon_task_name = os.getenv("AVALON_TASK") + + self.log.info(avalon_project) + self.log.info(avalon_asset) + + sg_project = _get_shotgrid_project(avalon_project) + sg_task = _get_shotgrid_task( + avalon_project, avalon_asset, avalon_task_name + ) + sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) + + if sg_project: + context.data["shotgridProject"] = sg_project + self.log.info( + "Collected correspondig shotgrid project : {}".format( + sg_project + ) + ) + + if sg_task: + context.data["shotgridTask"] = sg_task + self.log.info( + "Collected correspondig shotgrid task : {}".format(sg_task) + ) + + if sg_entity: + context.data["shotgridEntity"] = sg_entity + self.log.info( + "Collected correspondig shotgrid entity : {}".format(sg_entity) + ) + + def _find_existing_version(self, code, context): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["sg_task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["code", "is", code], + ] + + sg = context.data.get("shotgridSession") + return sg.find_one("Version", filters, []) + + +def _get_shotgrid_collection(project): + mongo_url = os.getenv("OPENPYPE_MONGO") + client = MongoClient(mongo_url) + return client.get_database("shotgrid_openpype").get_collection(project) + + +def _get_shotgrid_project_settings(project): + return get_project_settings(project).get("shotgrid", {}) + + +def _get_shotgrid_project(avalon_project): + proj_settings = _get_shotgrid_project_settings(avalon_project["name"]) + shotgrid_project_id = proj_settings.get("shotgrid_project_id") + if shotgrid_project_id: + return {"type": "Project", "id": shotgrid_project_id} + return {} + + +def _get_shotgrid_task(avalon_project, avalon_asset, avalon_task): + sg_col = _get_shotgrid_collection(avalon_project["name"]) + shotgrid_task_hierarchy_row = sg_col.find_one( + { + "type": "Task", + "_id": {"$regex": "^" + avalon_task + "_[0-9]*"}, + "parent": {"$regex": ".*," + avalon_asset["name"] + ","}, + } + ) + if shotgrid_task_hierarchy_row: + return {"type": "Task", "id": shotgrid_task_hierarchy_row["src_id"]} + return {} + + +def _get_shotgrid_entity(avalon_project, avalon_asset): + sg_col = _get_shotgrid_collection(avalon_project["name"]) + shotgrid_entity_hierarchy_row = sg_col.find_one( + {"_id": avalon_asset["name"]} + ) + if shotgrid_entity_hierarchy_row: + return { + "type": shotgrid_entity_hierarchy_row["type"], + "id": shotgrid_entity_hierarchy_row["src_id"], + } + return {} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py new file mode 100644 index 0000000000..65a5de9f22 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -0,0 +1,132 @@ +import os +import sys +import pyblish.api +import shotgun_api3 +from shotgun_api3.shotgun import AuthenticationFault +from openpype.lib import OpenPypeSettingsRegistry +from openpype.api import get_project_settings, get_system_settings + + +class CollectShotgridSession(pyblish.api.ContextPlugin): + """Collect shotgrid session using user credentials""" + + order = pyblish.api.CollectorOrder + label = "Shotgrid user session" + + def process(self, context): + + certificate_path = os.getenv("SHOTGUN_API_CACERTS") + if certificate_path is None or not os.path.exists(certificate_path): + self.log.info( + "SHOTGUN_API_CACERTS does not contains a valid \ + path: {}".format( + certificate_path + ) + ) + certificate_path = get_shotgrid_certificate() + self.log.info("Get Certificate from shotgrid_api") + + if not os.path.exists(certificate_path): + self.log.error( + "Could not find certificate in shotgun_api3: \ + {}".format( + certificate_path + ) + ) + return + + set_shotgrid_certificate(certificate_path) + self.log.info("Set Certificate: {}".format(certificate_path)) + + avalon_project = os.getenv("AVALON_PROJECT") + + shotgrid_settings = get_shotgrid_settings(avalon_project) + self.log.info("shotgrid settings: {}".format(shotgrid_settings)) + shotgrid_servers_settings = get_shotgrid_servers() + self.log.info( + "shotgrid_servers_settings: {}".format(shotgrid_servers_settings) + ) + + shotgrid_server = shotgrid_settings.get("shotgrid_server", "") + if not shotgrid_server: + self.log.error( + "No Shotgrid server found, please choose a credential" + "in script name and script key in OpenPype settings" + ) + + shotgrid_server_setting = shotgrid_servers_settings.get( + shotgrid_server, {} + ) + shotgrid_url = shotgrid_server_setting.get("shotgrid_url", "") + + shotgrid_script_name = shotgrid_server_setting.get( + "shotgrid_script_name", "" + ) + shotgrid_script_key = shotgrid_server_setting.get( + "shotgrid_script_key", "" + ) + if not shotgrid_script_name and not shotgrid_script_key: + self.log.error( + "No Shotgrid api credential found, please enter " + "script name and script key in OpenPype settings" + ) + + login = get_login() or os.getenv("OPENPYPE_SG_USER") + + if not login: + self.log.error( + "No Shotgrid login found, please " + "login to shotgrid withing openpype Tray" + ) + + session = shotgun_api3.Shotgun( + base_url=shotgrid_url, + script_name=shotgrid_script_name, + api_key=shotgrid_script_key, + sudo_as_login=login, + ) + + try: + session.preferences_read() + except AuthenticationFault: + raise ValueError( + "Could not connect to shotgrid {} with user {}".format( + shotgrid_url, login + ) + ) + + self.log.info( + "Logged to shotgrid {} with user {}".format(shotgrid_url, login) + ) + context.data["shotgridSession"] = session + context.data["shotgridUser"] = login + + +def get_shotgrid_certificate(): + shotgun_api_path = os.path.dirname(shotgun_api3.__file__) + return os.path.join(shotgun_api_path, "lib", "certifi", "cacert.pem") + + +def set_shotgrid_certificate(certificate): + os.environ["SHOTGUN_API_CACERTS"] = certificate + + +def get_shotgrid_settings(project): + return get_project_settings(project).get("shotgrid", {}) + + +def get_shotgrid_servers(): + return ( + get_system_settings() + .get("modules", {}) + .get("shotgrid", {}) + .get("shotgrid_settings", {}) + ) + + +def get_login(): + reg = OpenPypeSettingsRegistry() + try: + return str(reg.get_item("shotgrid_login")) + except Exception as e: + return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py new file mode 100644 index 0000000000..cfd2d10fd9 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -0,0 +1,77 @@ +import os +import pyblish.api + + +class IntegrateShotgridPublish(pyblish.api.InstancePlugin): + """ + Create published Files from representations and add it to version. If + representation is tagged add shotgrid review, it will add it in + path to movie for a movie file or path to frame for an image sequence. + """ + + order = pyblish.api.IntegratorOrder + 0.499 + label = "Shotgrid Published Files" + + def process(self, instance): + + context = instance.context + + self.sg = context.data.get("shotgridSession") + + shotgrid_version = instance.data.get("shotgridVersion") + + for representation in instance.data.get("representations", []): + + local_path = representation.get("published_path") + code = os.path.basename(local_path) + + if representation.get("tags", []): + continue + + published_file = self._find_existing_publish( + code, context, shotgrid_version + ) + + published_file_data = { + "project": context.data.get("shotgridProject"), + "code": code, + "entity": context.data.get("shotgridEntity"), + "task": context.data.get("shotgridTask"), + "version": shotgrid_version, + "path": {"local_path": local_path}, + } + if not published_file: + published_file = self._create_published(published_file_data) + self.log.info( + "Create Shotgrid PublishedFile: {}".format(published_file) + ) + else: + self.sg.update( + published_file["type"], + published_file["id"], + published_file_data, + ) + self.log.info( + "Update Shotgrid PublishedFile: {}".format(published_file) + ) + + if instance.data["family"] == "image": + self.sg.upload_thumbnail( + published_file["type"], published_file["id"], local_path + ) + instance.data["shotgridPublishedFile"] = published_file + + def _find_existing_publish(self, code, context, shotgrid_version): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["version", "is", shotgrid_version], + ["code", "is", code], + ] + return self.sg.find_one("PublishedFile", filters, []) + + def _create_published(self, published_file_data): + + return self.sg.create("PublishedFile", published_file_data) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py new file mode 100644 index 0000000000..a1b7140e22 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -0,0 +1,92 @@ +import os +import pyblish.api + + +class IntegrateShotgridVersion(pyblish.api.InstancePlugin): + """Integrate Shotgrid Version""" + + order = pyblish.api.IntegratorOrder + 0.497 + label = "Shotgrid Version" + + sg = None + + def process(self, instance): + + context = instance.context + self.sg = context.data.get("shotgridSession") + + # TODO: Use path template solver to build version code from settings + anatomy = instance.data.get("anatomyData", {}) + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + + version = self._find_existing_version(code, context) + + if not version: + version = self._create_version(code, context) + self.log.info("Create Shotgrid version: {}".format(version)) + else: + self.log.info("Use existing Shotgrid version: {}".format(version)) + + data_to_update = {} + status = context.data.get("intent", {}).get("value") + if status: + data_to_update["sg_status_list"] = status + + for representation in instance.data.get("representations", []): + local_path = representation.get("published_path") + code = os.path.basename(local_path) + + if "shotgridreview" in representation.get("tags", []): + + if representation["ext"] in ["mov", "avi"]: + self.log.info( + "Upload review: {} for version shotgrid {}".format( + local_path, version.get("id") + ) + ) + self.sg.upload( + "Version", + version.get("id"), + local_path, + field_name="sg_uploaded_movie", + ) + + data_to_update["sg_path_to_movie"] = local_path + + elif representation["ext"] in ["jpg", "png", "exr", "tga"]: + path_to_frame = local_path.replace("0000", "#") + data_to_update["sg_path_to_frames"] = path_to_frame + + self.log.info("Update Shotgrid version with {}".format(data_to_update)) + self.sg.update("Version", version["id"], data_to_update) + + instance.data["shotgridVersion"] = version + + def _find_existing_version(self, code, context): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["sg_task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["code", "is", code], + ] + return self.sg.find_one("Version", filters, []) + + def _create_version(self, code, context): + + version_data = { + "project": context.data.get("shotgridProject"), + "sg_task": context.data.get("shotgridTask"), + "entity": context.data.get("shotgridEntity"), + "code": code, + } + + return self.sg.create("Version", version_data) diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py new file mode 100644 index 0000000000..7343c47808 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py @@ -0,0 +1,36 @@ +import pyblish.api +import openpype.api + + +class ValidateShotgridUser(pyblish.api.ContextPlugin): + """ + Check if user is valid and have access to the project. + """ + + label = "Validate Shotgrid User" + order = openpype.api.ValidateContentsOrder + + def process(self, context): + sg = context.data.get('shotgridSession') + + login = context.data.get('shotgridUser') + self.log.info("Login shotgrid set in OpenPype is {}".format(login)) + project = context.data.get("shotgridProject") + self.log.info("Current shotgun project is {}".format(project)) + + if not (login and sg and project): + raise KeyError() + + user = sg.find_one( + "HumanUser", + [["login", "is", login]], + ["projects"] + ) + + self.log.info(user) + self.log.info(login) + user_projects_id = [p["id"] for p in user.get("projects", [])] + if not project.get('id') in user_projects_id: + raise PermissionError("Login {} don't have access to the project {}".format(login, project)) + + self.log.info("Login {} have access to the project {}".format(login, project)) diff --git a/openpype/modules/shotgrid/server/README.md b/openpype/modules/shotgrid/server/README.md new file mode 100644 index 0000000000..15e056ff3e --- /dev/null +++ b/openpype/modules/shotgrid/server/README.md @@ -0,0 +1,5 @@ + +### Shotgrid server + +Please refer to the external project that covers Openpype/Shotgrid communication: + - https://github.com/Ellipsanime/shotgrid-leecher diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py new file mode 100644 index 0000000000..75d5f843c5 --- /dev/null +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -0,0 +1,62 @@ +import os +import threading +from typing import Optional, Dict, Any + +from openpype_interfaces import ( + ITrayModule, + IPluginPaths, + ILaunchHookPaths, +) + +from openpype.modules import OpenPypeModule +from .aop.patch import patch_avalon_db +from .tray.shotgrid_tray import ( + ShotgridTrayWrapper, +) + +SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class ShotgridModule( + OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths +): + leecher_manager_url: str + name: str = "shotgrid" + enabled: bool = False + project_id: Optional[str] = None + tray_wrapper: ShotgridTrayWrapper + + def initialize(self, modules_settings: Dict[str, Any]): + patch_avalon_db() + threading.Timer(10.0, patch_avalon_db).start() + shotgrid_settings = modules_settings.get(self.name, dict()) + self.enabled = shotgrid_settings.get("enabled", False) + self.leecher_manager_url = shotgrid_settings.get( + "leecher_manager_url", "" + ) + + def connect_with_modules(self, enabled_modules): + pass + + def get_global_environments(self) -> Dict[str, Any]: + return {"PROJECT_ID": self.project_id} + + def get_plugin_paths(self) -> Dict[str, Any]: + return { + "publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")] + } + + def get_launch_hook_paths(self) -> str: + return os.path.join(SHOTGRID_MODULE_DIR, "hooks") + + def tray_init(self): + self.tray_wrapper = ShotgridTrayWrapper(self) + + def tray_start(self): + return self.tray_wrapper.validate() + + def tray_exit(self, *args, **kwargs): + return self.tray_wrapper + + def tray_menu(self, tray_menu): + return self.tray_wrapper.tray_menu(tray_menu) diff --git a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py new file mode 100644 index 0000000000..1f78cf77c9 --- /dev/null +++ b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py @@ -0,0 +1,34 @@ +import pytest +from assertpy import assert_that + +import openpype.modules.shotgrid.lib.credentials as sut + + +def test_missing_shotgrid_url(): + with pytest.raises(Exception) as ex: + # arrange + url = "" + # act + sut.get_shotgrid_hostname(url) + # assert + assert_that(ex).is_equal_to("Shotgrid url cannot be a null") + + +def test_full_shotgrid_url(): + # arrange + url = "https://shotgrid.com/myinstance" + # act + actual = sut.get_shotgrid_hostname(url) + # assert + assert_that(actual).is_not_empty() + assert_that(actual).is_equal_to("shotgrid.com") + + +def test_incomplete_shotgrid_url(): + # arrange + url = "shotgrid.com/myinstance" + # act + actual = sut.get_shotgrid_hostname(url) + # assert + assert_that(actual).is_not_empty() + assert_that(actual).is_equal_to("shotgrid.com") diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py new file mode 100644 index 0000000000..8d7d587c6a --- /dev/null +++ b/openpype/modules/shotgrid/tray/credential_dialog.py @@ -0,0 +1,202 @@ +import os +from typing import Any +from Qt import QtCore, QtWidgets, QtGui + +from openpype import style +from openpype import resources +from openpype.modules.shotgrid.lib import settings, credentials + + +class CredentialsDialog(QtWidgets.QDialog): + SIZE_W = 450 + SIZE_H = 200 + + _module: Any = None + _is_logged: bool = False + url_label: QtWidgets.QLabel + login_label: QtWidgets.QLabel + password_label: QtWidgets.QLabel + url_input: QtWidgets.QComboBox + login_input: QtWidgets.QLineEdit + password_input: QtWidgets.QLineEdit + input_layout: QtWidgets.QFormLayout + login_button: QtWidgets.QPushButton + buttons_layout: QtWidgets.QHBoxLayout + main_widget: QtWidgets.QVBoxLayout + + login_changed: QtCore.Signal = QtCore.Signal() + + def __init__(self, module, parent=None): + super(CredentialsDialog, self).__init__(parent) + + self._module = module + self._is_logged = False + + self.setWindowTitle("OpenPype - Shotgrid Login") + + icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) + self.setWindowIcon(icon) + + self.setWindowFlags( + QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowMinimizeButtonHint + ) + self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) + self.setStyleSheet(style.load_stylesheet()) + + self.ui_init() + + def ui_init(self): + self.url_label = QtWidgets.QLabel("Shotgrid server:") + self.login_label = QtWidgets.QLabel("Login:") + self.password_label = QtWidgets.QLabel("Password:") + + self.url_input = QtWidgets.QComboBox() + # self.url_input.setReadOnly(True) + + self.login_input = QtWidgets.QLineEdit() + self.login_input.setPlaceholderText("login") + + self.password_input = QtWidgets.QLineEdit() + self.password_input.setPlaceholderText("password") + self.password_input.setEchoMode(QtWidgets.QLineEdit.Password) + + self.error_label = QtWidgets.QLabel("") + self.error_label.setStyleSheet("color: red;") + self.error_label.setWordWrap(True) + self.error_label.hide() + + self.input_layout = QtWidgets.QFormLayout() + self.input_layout.setContentsMargins(10, 15, 10, 5) + + self.input_layout.addRow(self.url_label, self.url_input) + self.input_layout.addRow(self.login_label, self.login_input) + self.input_layout.addRow(self.password_label, self.password_input) + self.input_layout.addRow(self.error_label) + + self.login_button = QtWidgets.QPushButton("Login") + self.login_button.setToolTip("Log in shotgrid instance") + self.login_button.clicked.connect(self._on_shotgrid_login_clicked) + + self.logout_button = QtWidgets.QPushButton("Logout") + self.logout_button.setToolTip("Log out shotgrid instance") + self.logout_button.clicked.connect(self._on_shotgrid_logout_clicked) + + self.buttons_layout = QtWidgets.QHBoxLayout() + self.buttons_layout.addWidget(self.logout_button) + self.buttons_layout.addWidget(self.login_button) + + self.main_widget = QtWidgets.QVBoxLayout(self) + self.main_widget.addLayout(self.input_layout) + self.main_widget.addLayout(self.buttons_layout) + self.setLayout(self.main_widget) + + def show(self, *args, **kwargs): + super(CredentialsDialog, self).show(*args, **kwargs) + self._fill_shotgrid_url() + self._fill_shotgrid_login() + + def _fill_shotgrid_url(self): + servers = settings.get_shotgrid_servers() + + if servers: + for _, v in servers.items(): + self.url_input.addItem("{}".format(v.get('shotgrid_url'))) + self._valid_input(self.url_input) + self.login_button.show() + self.logout_button.show() + enabled = True + else: + self.set_error("Ask your admin to add shotgrid server in settings") + self._invalid_input(self.url_input) + self.login_button.hide() + self.logout_button.hide() + enabled = False + + self.login_input.setEnabled(enabled) + self.password_input.setEnabled(enabled) + + def _fill_shotgrid_login(self): + login = credentials.get_local_login() + + if login: + self.login_input.setText(login) + + def _clear_shotgrid_login(self): + self.login_input.setText("") + self.password_input.setText("") + + def _on_shotgrid_login_clicked(self): + login = self.login_input.text().strip() + password = self.password_input.text().strip() + missing = [] + + if login == "": + missing.append("login") + self._invalid_input(self.login_input) + + if password == "": + missing.append("password") + self._invalid_input(self.password_input) + + url = self.url_input.currentText() + if url == "": + missing.append("url") + self._invalid_input(self.url_input) + + if len(missing) > 0: + self.set_error("You didn't enter {}".format(" and ".join(missing))) + return + + # if credentials.check_credentials( + # login=login, + # password=password, + # shotgrid_url=url, + # ): + credentials.save_local_login( + login=login + ) + os.environ['OPENPYPE_SG_USER'] = login + self._on_login() + + self.set_error("CANT LOGIN") + + def _on_shotgrid_logout_clicked(self): + credentials.clear_local_login() + del os.environ['OPENPYPE_SG_USER'] + self._clear_shotgrid_login() + self._on_logout() + + def set_error(self, msg: str): + self.error_label.setText(msg) + self.error_label.show() + + def _on_login(self): + self._is_logged = True + self.login_changed.emit() + self._close_widget() + + def _on_logout(self): + self._is_logged = False + self.login_changed.emit() + + def _close_widget(self): + self.hide() + + def _valid_input(self, input_widget: QtWidgets.QLineEdit): + input_widget.setStyleSheet("") + + def _invalid_input(self, input_widget: QtWidgets.QLineEdit): + input_widget.setStyleSheet("border: 1px solid red;") + + def login_with_credentials( + self, url: str, login: str, password: str + ) -> bool: + verification = credentials.check_credentials(url, login, password) + if verification: + credentials.save_credentials(login, password, False) + self._module.set_credentials_to_env(login, password) + self.set_credentials(login, password) + self.login_changed.emit() + return verification diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py new file mode 100644 index 0000000000..0be58e3b20 --- /dev/null +++ b/openpype/modules/shotgrid/tray/shotgrid_tray.py @@ -0,0 +1,76 @@ +import os +import webbrowser +from typing import Any + +from Qt import QtWidgets + +from openpype.modules.shotgrid.lib import credentials +from openpype.modules.shotgrid.tray.credential_dialog import ( + CredentialsDialog, +) + + +class ShotgridTrayWrapper: + module: Any + credentials_dialog: CredentialsDialog + logged_user_label: QtWidgets.QAction + + def __init__(self, module) -> None: + self.module = module + self.credentials_dialog = CredentialsDialog(module) + self.credentials_dialog.login_changed.connect(self.set_login_label) + self.logged_user_label = QtWidgets.QAction("") + self.logged_user_label.setDisabled(True) + self.set_login_label() + + def show_batch_dialog(self): + if self.module.leecher_manager_url: + webbrowser.open(self.module.leecher_manager_url) + + def show_connect_dialog(self): + self.show_credential_dialog() + + def show_credential_dialog(self): + self.credentials_dialog.show() + self.credentials_dialog.activateWindow() + self.credentials_dialog.raise_() + + def set_login_label(self): + login = credentials.get_local_login() + if login: + self.logged_user_label.setText("{}".format(login)) + else: + self.logged_user_label.setText( + "No User logged in {0}".format(login) + ) + + def tray_menu(self, tray_menu): + # Add login to user menu + menu = QtWidgets.QMenu("Shotgrid", tray_menu) + show_connect_action = QtWidgets.QAction("Connect to Shotgrid", menu) + show_connect_action.triggered.connect(self.show_connect_dialog) + menu.addAction(self.logged_user_label) + menu.addSeparator() + menu.addAction(show_connect_action) + tray_menu.addMenu(menu) + + # Add manager to Admin menu + for m in tray_menu.findChildren(QtWidgets.QMenu): + if m.title() == "Admin": + shotgrid_manager_action = QtWidgets.QAction( + "Shotgrid manager", menu + ) + shotgrid_manager_action.triggered.connect( + self.show_batch_dialog + ) + m.addAction(shotgrid_manager_action) + + def validate(self) -> bool: + login = credentials.get_local_login() + + if not login: + self.show_credential_dialog() + else: + os.environ["OPENPYPE_SG_USER"] = login + + return True diff --git a/openpype/resources/app_icons/shotgrid.png b/openpype/resources/app_icons/shotgrid.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0cc047f9ed86e0db45ea557404ab7edb2f5bf2 GIT binary patch literal 45744 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 N8$~sR3OTc&{{b(fTU!7C literal 0 HcmV?d00001 diff --git a/openpype/settings/defaults/project_settings/shotgrid.json b/openpype/settings/defaults/project_settings/shotgrid.json new file mode 100644 index 0000000000..83b6f69074 --- /dev/null +++ b/openpype/settings/defaults/project_settings/shotgrid.json @@ -0,0 +1,22 @@ +{ + "shotgrid_project_id": 0, + "shotgrid_server": "", + "event": { + "enabled": false + }, + "fields": { + "asset": { + "type": "sg_asset_type" + }, + "sequence": { + "episode_link": "episode" + }, + "shot": { + "episode_link": "sg_episode", + "sequence_link": "sg_sequence" + }, + "task": { + "step": "step" + } + } +} diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index d74269922f..a5746e930b 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -137,6 +137,13 @@ } } }, + "shotgrid": { + "enabled": true, + "filter_projects_by_login": true, + "leecher_manager_url": "http://127.0.0.1:3000", + "leecher_backend_url": "http://127.0.0.1:8090", + "shotgrid_settings": {} + }, "timers_manager": { "enabled": true, "auto_stop": true, @@ -205,4 +212,4 @@ "linux": "" } } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index a173e2454f..b2cb2204f4 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -107,6 +107,7 @@ from .enum_entity import ( TaskTypeEnumEntity, DeadlineUrlEnumEntity, AnatomyTemplatesEnumEntity, + ShotgridUrlEnumEntity ) from .list_entity import ListEntity @@ -171,6 +172,7 @@ __all__ = ( "ToolsEnumEntity", "TaskTypeEnumEntity", "DeadlineUrlEnumEntity", + "ShotgridUrlEnumEntity", "AnatomyTemplatesEnumEntity", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 92a397afba..643bd5735d 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -1,10 +1,7 @@ import copy from .input_entities import InputEntity from .exceptions import EntitySchemaError -from .lib import ( - NOT_SET, - STRING_TYPE -) +from .lib import NOT_SET, STRING_TYPE class BaseEnumEntity(InputEntity): @@ -26,15 +23,13 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = "Key \"{}\" is more than once in enum items.".format( - key - ) + reason = 'Key "{}" is more than once in enum items.'.format(key) raise EntitySchemaError(self, reason) enum_keys.add(key) if not isinstance(key, STRING_TYPE): - reason = "Key \"{}\" has invalid type {}, expected {}.".format( + reason = 'Key "{}" has invalid type {}, expected {}.'.format( key, type(key), STRING_TYPE ) raise EntitySchemaError(self, reason) @@ -59,7 +54,7 @@ class BaseEnumEntity(InputEntity): for item in check_values: if item not in self.valid_keys: raise ValueError( - "{} Invalid value \"{}\". Expected one of: {}".format( + '{} Invalid value "{}". Expected one of: {}'.format( self.path, item, self.valid_keys ) ) @@ -84,7 +79,7 @@ class EnumEntity(BaseEnumEntity): self.valid_keys = set(all_keys) if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) value_on_not_set = [] if enum_default: if not isinstance(enum_default, list): @@ -109,7 +104,7 @@ class EnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -152,6 +147,7 @@ class HostsEnumEntity(BaseEnumEntity): Host name is not the same as application name. Host name defines implementation instead of application name. """ + schema_types = ["hosts-enum"] all_host_names = [ "aftereffects", @@ -169,7 +165,7 @@ class HostsEnumEntity(BaseEnumEntity): "tvpaint", "unreal", "standalonepublisher", - "webpublisher" + "webpublisher", ] def _item_initialization(self): @@ -210,7 +206,7 @@ class HostsEnumEntity(BaseEnumEntity): self.valid_keys = valid_keys if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.value_on_not_set = [] else: for key in valid_keys: @@ -218,7 +214,7 @@ class HostsEnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -226,14 +222,10 @@ class HostsEnumEntity(BaseEnumEntity): def schema_validations(self): if self.hosts_filter: enum_len = len(self.enum_items) - if ( - enum_len == 0 - or (enum_len == 1 and self.use_empty_value) - ): - joined_filters = ", ".join([ - '"{}"'.format(item) - for item in self.hosts_filter - ]) + if enum_len == 0 or (enum_len == 1 and self.use_empty_value): + joined_filters = ", ".join( + ['"{}"'.format(item) for item in self.hosts_filter] + ) reason = ( "All host names were removed after applying" " host filters. {}" @@ -246,24 +238,25 @@ class HostsEnumEntity(BaseEnumEntity): invalid_filters.add(item) if invalid_filters: - joined_filters = ", ".join([ - '"{}"'.format(item) - for item in self.hosts_filter - ]) - expected_hosts = ", ".join([ - '"{}"'.format(item) - for item in self.all_host_names - ]) - self.log.warning(( - "Host filters containt invalid host names:" - " \"{}\" Expected values are {}" - ).format(joined_filters, expected_hosts)) + joined_filters = ", ".join( + ['"{}"'.format(item) for item in self.hosts_filter] + ) + expected_hosts = ", ".join( + ['"{}"'.format(item) for item in self.all_host_names] + ) + self.log.warning( + ( + "Host filters containt invalid host names:" + ' "{}" Expected values are {}' + ).format(joined_filters, expected_hosts) + ) super(HostsEnumEntity, self).schema_validations() class AppsEnumEntity(BaseEnumEntity): """Enum of applications for project anatomy attributes.""" + schema_types = ["apps-enum"] def _item_initialization(self): @@ -271,7 +264,7 @@ class AppsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.placeholder = None def _get_enum_values(self): @@ -352,7 +345,7 @@ class ToolsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.placeholder = None def _get_enum_values(self): @@ -409,10 +402,10 @@ class TaskTypeEnumEntity(BaseEnumEntity): def _item_initialization(self): self.multiselection = self.schema_data.get("multiselection", True) if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.value_on_not_set = [] else: - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) self.value_on_not_set = "" self.enum_items = [] @@ -507,7 +500,8 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): enum_items_list = [] for server_name, url_entity in deadline_urls_entity.items(): enum_items_list.append( - {server_name: "{}: {}".format(server_name, url_entity.value)}) + {server_name: "{}: {}".format(server_name, url_entity.value)} + ) valid_keys.add(server_name) return enum_items_list, valid_keys @@ -530,6 +524,50 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): self._current_value = tuple(self.valid_keys)[0] +class ShotgridUrlEnumEntity(BaseEnumEntity): + schema_types = ["shotgrid_url-enum"] + + def _item_initialization(self): + self.multiselection = False + + self.enum_items = [] + self.valid_keys = set() + + self.valid_value_types = (STRING_TYPE,) + self.value_on_not_set = "" + + # GUI attribute + self.placeholder = self.schema_data.get("placeholder") + + def _get_enum_values(self): + shotgrid_settings = self.get_entity_from_path( + "system_settings/modules/shotgrid/shotgrid_settings" + ) + + valid_keys = set() + enum_items_list = [] + for server_name, settings in shotgrid_settings.items(): + enum_items_list.append( + { + server_name: "{}: {}".format( + server_name, settings["shotgrid_url"].value + ) + } + ) + valid_keys.add(server_name) + return enum_items_list, valid_keys + + def set_override_state(self, *args, **kwargs): + super(ShotgridUrlEnumEntity, self).set_override_state(*args, **kwargs) + + self.enum_items, self.valid_keys = self._get_enum_values() + if not self.valid_keys: + self._current_value = "" + + elif self._current_value not in self.valid_keys: + self._current_value = tuple(self.valid_keys)[0] + + class AnatomyTemplatesEnumEntity(BaseEnumEntity): schema_types = ["anatomy-templates-enum"] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 8e4eba86ef..521c066964 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -62,6 +62,10 @@ "type": "schema", "name": "schema_project_ftrack" }, + { + "type": "schema", + "name": "schema_project_shotgrid" + }, { "type": "schema", "name": "schema_project_deadline" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json new file mode 100644 index 0000000000..4faeca89f3 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json @@ -0,0 +1,98 @@ +{ + "type": "dict", + "key": "shotgrid", + "label": "Shotgrid", + "collapsible": true, + "is_file": true, + "children": [ + { + "type": "number", + "key": "shotgrid_project_id", + "label": "Shotgrid project id" + }, + { + "type": "shotgrid_url-enum", + "key": "shotgrid_server", + "label": "Shotgrid Server" + }, + { + "type": "dict", + "key": "event", + "label": "Event Handler", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "fields", + "label": "Fields Template", + "collapsible": true, + "children": [ + { + "type": "dict", + "key": "asset", + "label": "Asset", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "type", + "label": "Asset Type" + } + ] + }, + { + "type": "dict", + "key": "sequence", + "label": "Sequence", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "episode_link", + "label": "Episode link" + } + ] + }, + { + "type": "dict", + "key": "shot", + "label": "Shot", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "episode_link", + "label": "Episode link" + }, + { + "type": "text", + "key": "sequence_link", + "label": "Sequence link" + } + ] + }, + { + "type": "dict", + "key": "task", + "label": "Task", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "step", + "label": "Step link" + } + ] + } + ] + } + ] +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index 484fbf9d07..a4b28f47bc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -13,6 +13,9 @@ { "ftrackreview": "Add review to Ftrack" }, + { + "shotgridreview": "Add review to Shotgrid" + }, { "delete": "Delete output" }, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 52595914ed..dacbc8c3a1 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -44,6 +44,60 @@ "type": "schema", "name": "schema_ftrack" }, + { + "type": "dict", + "key": "shotgrid", + "label": "Shotgrid", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "leecher_manager_url", + "label": "Shotgrid Leecher Manager URL" + }, + { + "type": "text", + "key": "leecher_backend_url", + "label": "Shotgrid Leecher Backend URL" + }, + { + "type": "boolean", + "key": "filter_projects_by_login", + "label": "Filter projects by SG login" + }, + { + "type": "dict-modifiable", + "key": "shotgrid_settings", + "label": "Shotgrid Servers", + "object_type": { + "type": "dict", + "children": [ + { + "key": "shotgrid_url", + "label": "Server URL", + "type": "text" + }, + { + "key": "shotgrid_script_name", + "label": "Script Name", + "type": "text" + }, + { + "key": "shotgrid_script_key", + "label": "Script api key", + "type": "text" + } + ] + } + } + ] + }, { "type": "dict", "key": "timers_manager", diff --git a/pyproject.toml b/pyproject.toml index 006f6eb4e5..8d9eb8b050 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" +shotgun_api3 = {git = "https://github.com/shotgunsoftware/python-api.git", rev = "v3.3.3"} google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" keyring = "^22.0.1" From 00285cacc0d01dea62495cdb04f7155cb5d3da1d Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:47:29 +0100 Subject: [PATCH 002/258] Fix hound complaints --- .../publish/collect_shotgrid_session.py | 3 +-- .../plugins/publish/validate_shotgrid_user.py | 24 ++++++++++--------- openpype/modules/shotgrid/shotgrid_module.py | 4 +++- openpype/settings/entities/enum_entity.py | 4 +++- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index 65a5de9f22..b9eead6244 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -1,5 +1,4 @@ import os -import sys import pyblish.api import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault @@ -128,5 +127,5 @@ def get_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) - except Exception as e: + except Exception as _: return None diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py index 7343c47808..c14c980e2a 100644 --- a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py +++ b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py @@ -4,16 +4,16 @@ import openpype.api class ValidateShotgridUser(pyblish.api.ContextPlugin): """ - Check if user is valid and have access to the project. + Check if user is valid and have access to the project. """ label = "Validate Shotgrid User" order = openpype.api.ValidateContentsOrder def process(self, context): - sg = context.data.get('shotgridSession') + sg = context.data.get("shotgridSession") - login = context.data.get('shotgridUser') + login = context.data.get("shotgridUser") self.log.info("Login shotgrid set in OpenPype is {}".format(login)) project = context.data.get("shotgridProject") self.log.info("Current shotgun project is {}".format(project)) @@ -21,16 +21,18 @@ class ValidateShotgridUser(pyblish.api.ContextPlugin): if not (login and sg and project): raise KeyError() - user = sg.find_one( - "HumanUser", - [["login", "is", login]], - ["projects"] - ) + user = sg.find_one("HumanUser", [["login", "is", login]], ["projects"]) self.log.info(user) self.log.info(login) user_projects_id = [p["id"] for p in user.get("projects", [])] - if not project.get('id') in user_projects_id: - raise PermissionError("Login {} don't have access to the project {}".format(login, project)) + if not project.get("id") in user_projects_id: + raise PermissionError( + "Login {} don't have access to the project {}".format( + login, project + ) + ) - self.log.info("Login {} have access to the project {}".format(login, project)) + self.log.info( + "Login {} have access to the project {}".format(login, project) + ) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 75d5f843c5..222c4c2e1f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -43,7 +43,9 @@ class ShotgridModule( def get_plugin_paths(self) -> Dict[str, Any]: return { - "publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")] + "publish": [ + os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") + ] } def get_launch_hook_paths(self) -> str: diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 643bd5735d..3b3dd47e61 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -23,7 +23,9 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = 'Key "{}" is more than once in enum items.'.format(key) + reason = 'Key "{}" is more than once in enum items.'.format( + key + ) raise EntitySchemaError(self, reason) enum_keys.add(key) From feae04f0c34abdd87bcb6703962ac66c98e33831 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:49:42 +0100 Subject: [PATCH 003/258] Fix hound Exception complaints --- .../shotgrid/plugins/publish/collect_shotgrid_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index b9eead6244..edf804a892 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -127,5 +127,5 @@ def get_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) - except Exception as _: + except Exception: return None From 521b56289dea9c7697dfc44bf8d8095278e377c9 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:07:49 +0100 Subject: [PATCH 004/258] Add fixes after first PR --- .editorconfig | 19 -------------- .gitignore | 3 +++ .pre-commit-config.yaml | 26 ------------------- mypy.ini | 5 ---- .../plugins/publish/submit_publish_job.py | 1 - openpype/modules/shotgrid/lib/settings.py | 3 +++ .../publish/collect_shotgrid_session.py | 10 +------ 7 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .pre-commit-config.yaml delete mode 100644 mypy.ini diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index aeb5534872..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true - -[*.{js,py}] -charset = utf-8 - -[*.py] -indent_style = space -indent_size = 4 - -[*.yml] -indent_style = space -indent_size = 2 - -[Makefile] -indent_style = tab diff --git a/.gitignore b/.gitignore index fa3fae1ad2..f90549d0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ website/.docusaurus .poetry/ .python-version +.editorconfig +.pre-commit-config.yaml +mypy.ini diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 6a986c7dd9..0000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files -- repo: https://github.com/ambv/black - rev: 21.4b0 - hooks: - - id: black - language_version: "3" - args: - - "--config" - - "./pyproject.toml" -- repo: https://github.com/pycqa/flake8 - rev: "3.9.2" # pick a git hash / tag to point to - hooks: - - id: flake8 -- repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.902' - hooks: - - id: mypy - args: [--no-strict-optional, --ignore-missing-imports] - additional_dependencies: [tokenize-rt==3.2.0] diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 90cde26676..0000000000 --- a/mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -python_version = 3.7 -ignore_missing_imports = false -check_untyped_defs = true -follow_imports = silent diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index e1e1ea6b94..3c4e0d2913 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -113,7 +113,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "celaction": [r".*"]} enviro_filter = [ - "OPENPYPE_SG_USER", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 0f4fc235cc..b34407fbf5 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,4 +1,5 @@ import os +from functools import lru_cache from typing import Tuple, Dict, List, Any from pymongo import MongoClient @@ -13,10 +14,12 @@ def get_project_list() -> List[str]: return db.list_collection_names() +@lru_cache(maxsize=64) def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: return get_project_settings(project).get(MODULE_NAME, {}) +@lru_cache(maxsize=64) def get_shotgrid_settings() -> Dict[str, Any]: return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index edf804a892..60071ad2fc 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -4,6 +4,7 @@ import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault from openpype.lib import OpenPypeSettingsRegistry from openpype.api import get_project_settings, get_system_settings +from openpype.modules.shotgrid.lib.settings import get_shotgrid_servers class CollectShotgridSession(pyblish.api.ContextPlugin): @@ -114,15 +115,6 @@ def get_shotgrid_settings(project): return get_project_settings(project).get("shotgrid", {}) -def get_shotgrid_servers(): - return ( - get_system_settings() - .get("modules", {}) - .get("shotgrid", {}) - .get("shotgrid_settings", {}) - ) - - def get_login(): reg = OpenPypeSettingsRegistry() try: From 68941da3c8c08c5995c933d8b21317de6ddb73cc Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:33:23 +0100 Subject: [PATCH 005/258] Add fixes for hound --- .../plugins/publish/collect_shotgrid_entities.py | 10 ++++------ .../plugins/publish/collect_shotgrid_session.py | 14 +++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index b89dda3a80..6778b26550 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,9 @@ import os + import pyblish.api from pymongo import MongoClient -from openpype.api import get_project_settings + +from openpype.modules.shotgrid.lib.settings import get_shotgrid_project_settings class CollectShotgridEntities(pyblish.api.ContextPlugin): @@ -64,12 +66,8 @@ def _get_shotgrid_collection(project): return client.get_database("shotgrid_openpype").get_collection(project) -def _get_shotgrid_project_settings(project): - return get_project_settings(project).get("shotgrid", {}) - - def _get_shotgrid_project(avalon_project): - proj_settings = _get_shotgrid_project_settings(avalon_project["name"]) + proj_settings = get_shotgrid_project_settings(avalon_project["name"]) shotgrid_project_id = proj_settings.get("shotgrid_project_id") if shotgrid_project_id: return {"type": "Project", "id": shotgrid_project_id} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index 60071ad2fc..9d5d2271bf 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -1,10 +1,14 @@ import os + import pyblish.api import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault + from openpype.lib import OpenPypeSettingsRegistry -from openpype.api import get_project_settings, get_system_settings -from openpype.modules.shotgrid.lib.settings import get_shotgrid_servers +from openpype.modules.shotgrid.lib.settings import ( + get_shotgrid_servers, + get_shotgrid_project_settings, +) class CollectShotgridSession(pyblish.api.ContextPlugin): @@ -40,7 +44,7 @@ class CollectShotgridSession(pyblish.api.ContextPlugin): avalon_project = os.getenv("AVALON_PROJECT") - shotgrid_settings = get_shotgrid_settings(avalon_project) + shotgrid_settings = get_shotgrid_project_settings(avalon_project) self.log.info("shotgrid settings: {}".format(shotgrid_settings)) shotgrid_servers_settings = get_shotgrid_servers() self.log.info( @@ -111,10 +115,6 @@ def set_shotgrid_certificate(certificate): os.environ["SHOTGUN_API_CACERTS"] = certificate -def get_shotgrid_settings(project): - return get_project_settings(project).get("shotgrid", {}) - - def get_login(): reg = OpenPypeSettingsRegistry() try: From 1214cb995de0ae33ed52ab788c3df00f4655ac8d Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:36:27 +0100 Subject: [PATCH 006/258] Remove an empty line --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index 2fa14cea6f..2200525320 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb +Subproject commit 2200525320923f17df3b4c3b19ebd737c8a7e625 From d106ba4694ab542f4c496d2b1cc2e5d693386657 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:38:12 +0100 Subject: [PATCH 007/258] Reformat code after hound barking --- .../shotgrid/plugins/publish/collect_shotgrid_entities.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 6778b26550..a770c1eb87 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -3,7 +3,9 @@ import os import pyblish.api from pymongo import MongoClient -from openpype.modules.shotgrid.lib.settings import get_shotgrid_project_settings +from openpype.modules.shotgrid.lib.settings import ( + get_shotgrid_project_settings, +) class CollectShotgridEntities(pyblish.api.ContextPlugin): From 3c1ecfb36e1e29a7d3eb0b3845356dd9f8cda155 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 8 Apr 2022 15:12:29 +0200 Subject: [PATCH 008/258] Drawn back to the obsolete version of python --- openpype/modules/shotgrid/aop/patch.py | 11 +- openpype/modules/shotgrid/lib/credentials.py | 30 +- openpype/modules/shotgrid/lib/record.py | 18 +- openpype/modules/shotgrid/lib/server.py | 3 +- openpype/modules/shotgrid/lib/settings.py | 15 +- openpype/modules/shotgrid/shotgrid_module.py | 19 +- .../shotgrid/tray/credential_dialog.py | 37 +- .../modules/shotgrid/tray/shotgrid_tray.py | 11 +- poetry.lock | 812 +++++++++--------- 9 files changed, 493 insertions(+), 463 deletions(-) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 6ca81033e2..208ca715d3 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -1,5 +1,4 @@ from copy import copy -from typing import Any, Iterator, Dict, Set from avalon.api import AvalonMongoDB @@ -14,8 +13,8 @@ _LOG = Logger().get_logger("ShotgridModule.patch") def _patched_projects( - self: Any, projection: Any = None, only_active: bool = True -) -> Iterator[Dict[str, Any]]: + self, projection=None, only_active=True +): all_projects = list(self._prev_projects(projection, only_active)) if ( not credentials.get_local_login() @@ -30,18 +29,18 @@ def _patched_projects( return all_projects -def _upper(x: Any) -> str: +def _upper(x): return str(x).strip().upper() -def _fetch_linked_project_names() -> Set[str]: +def _fetch_linked_project_names(): return { _upper(x["project_name"]) for x in server.find_linked_projects(credentials.get_local_login()) } -def patch_avalon_db() -> None: +def patch_avalon_db(): _LOG.debug("Run avalon patching") if AvalonMongoDB.projects is _patched_projects: return None diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py index a334968cda..337c4f6ecb 100644 --- a/openpype/modules/shotgrid/lib/credentials.py +++ b/openpype/modules/shotgrid/lib/credentials.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional + from urllib.parse import urlparse import shotgun_api3 @@ -8,21 +8,21 @@ from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry from openpype.modules.shotgrid.lib.record import Credentials -def _get_shotgrid_secure_key(hostname: str, key: str) -> str: +def _get_shotgrid_secure_key(hostname, key): """Secure item key for entered hostname.""" return f"shotgrid/{hostname}/{key}" def _get_secure_value_and_registry( - hostname: str, - name: str, -) -> Tuple[str, OpenPypeSecureRegistry]: + hostname, + name, +): key = _get_shotgrid_secure_key(hostname, name) registry = OpenPypeSecureRegistry(key) return registry.get_item(name, None), registry -def get_shotgrid_hostname(shotgrid_url: str) -> str: +def get_shotgrid_hostname(shotgrid_url): if not shotgrid_url: raise Exception("Shotgrid url cannot be a null") @@ -35,7 +35,7 @@ def get_shotgrid_hostname(shotgrid_url: str) -> str: # Credentials storing function (using keyring) -def get_credentials(shotgrid_url: str) -> Optional[Credentials]: +def get_credentials(shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) if not hostname: return None @@ -50,7 +50,7 @@ def get_credentials(shotgrid_url: str) -> Optional[Credentials]: return Credentials(login_value, password_value) -def save_credentials(login: str, password: str, shotgrid_url: str): +def save_credentials(login, password, shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) _, login_registry = _get_secure_value_and_registry( hostname, @@ -65,7 +65,7 @@ def save_credentials(login: str, password: str, shotgrid_url: str): password_registry.set_item(Credentials.password_key_prefix(), password) -def clear_credentials(shotgrid_url: str): +def clear_credentials(shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) login_value, login_registry = _get_secure_value_and_registry( hostname, @@ -86,7 +86,7 @@ def clear_credentials(shotgrid_url: str): # Login storing function (using json) -def get_local_login() -> Optional[str]: +def get_local_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) @@ -94,7 +94,7 @@ def get_local_login() -> Optional[str]: return None -def save_local_login(login: str): +def save_local_login(login): reg = OpenPypeSettingsRegistry() reg.set_item("shotgrid_login", login) @@ -105,10 +105,10 @@ def clear_local_login(): def check_credentials( - login: str, - password: str, - shotgrid_url: str, -) -> bool: + login, + password, + shotgrid_url, +): if not shotgrid_url or not login or not password: return False diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py index 1796e6c019..f62f4855d5 100644 --- a/openpype/modules/shotgrid/lib/record.py +++ b/openpype/modules/shotgrid/lib/record.py @@ -1,18 +1,20 @@ -from dataclasses import dataclass - -@dataclass(frozen=True) class Credentials: - login: str - password: str + login = None + password = None - def is_empty(self) -> bool: + def __init__(self, login, password) -> None: + super().__init__() + self.login = login + self.password = password + + def is_empty(self): return not (self.login and self.password) @staticmethod - def login_key_prefix() -> str: + def login_key_prefix(): return "login" @staticmethod - def password_key_prefix() -> str: + def password_key_prefix(): return "password" diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py index 0fe4b8429e..50645d4089 100644 --- a/openpype/modules/shotgrid/lib/server.py +++ b/openpype/modules/shotgrid/lib/server.py @@ -1,7 +1,6 @@ import traceback import requests -from typing import Dict, Any, List from openpype.api import Logger from openpype.modules.shotgrid.lib import ( @@ -11,7 +10,7 @@ from openpype.modules.shotgrid.lib import ( _LOG = Logger().get_logger("ShotgridModule.server") -def find_linked_projects(email: str) -> List[Dict[str, Any]]: +def find_linked_projects(email): url = "".join( [ settings_lib.get_leecher_backend_url(), diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index b34407fbf5..954b96f3c2 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,13 +1,12 @@ import os from functools import lru_cache -from typing import Tuple, Dict, List, Any from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -def get_project_list() -> List[str]: +def get_project_list(): mongo_url = os.getenv("OPENPYPE_MONGO") client = MongoClient(mongo_url) db = client['avalon'] @@ -15,28 +14,28 @@ def get_project_list() -> List[str]: @lru_cache(maxsize=64) -def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: +def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) @lru_cache(maxsize=64) -def get_shotgrid_settings() -> Dict[str, Any]: +def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) -def get_shotgrid_servers() -> Dict[str, Any]: +def get_shotgrid_servers(): return get_shotgrid_settings().get("shotgrid_settings", {}) -def get_leecher_backend_url() -> str: +def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") -def filter_projects_by_login() -> bool: +def filter_projects_by_login(): return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) -def get_shotgrid_event_mongo_info() -> Tuple[str, str]: +def get_shotgrid_event_mongo_info(): database_name = os.environ["OPENPYPE_DATABASE_NAME"] collection_name = "shotgrid_events" return database_name, collection_name diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 222c4c2e1f..1f05ae5a52 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,6 +1,5 @@ import os import threading -from typing import Optional, Dict, Any from openpype_interfaces import ( ITrayModule, @@ -20,13 +19,13 @@ SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) class ShotgridModule( OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths ): - leecher_manager_url: str - name: str = "shotgrid" - enabled: bool = False - project_id: Optional[str] = None - tray_wrapper: ShotgridTrayWrapper + leecher_manager_url = None + name = "shotgrid" + enabled = False + project_id = None + tray_wrapper = None - def initialize(self, modules_settings: Dict[str, Any]): + def initialize(self, modules_settings): patch_avalon_db() threading.Timer(10.0, patch_avalon_db).start() shotgrid_settings = modules_settings.get(self.name, dict()) @@ -38,17 +37,17 @@ class ShotgridModule( def connect_with_modules(self, enabled_modules): pass - def get_global_environments(self) -> Dict[str, Any]: + def get_global_environments(self): return {"PROJECT_ID": self.project_id} - def get_plugin_paths(self) -> Dict[str, Any]: + def get_plugin_paths(self): return { "publish": [ os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") ] } - def get_launch_hook_paths(self) -> str: + def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") def tray_init(self): diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py index 8d7d587c6a..9d841d98be 100644 --- a/openpype/modules/shotgrid/tray/credential_dialog.py +++ b/openpype/modules/shotgrid/tray/credential_dialog.py @@ -1,5 +1,4 @@ import os -from typing import Any from Qt import QtCore, QtWidgets, QtGui from openpype import style @@ -11,20 +10,20 @@ class CredentialsDialog(QtWidgets.QDialog): SIZE_W = 450 SIZE_H = 200 - _module: Any = None - _is_logged: bool = False - url_label: QtWidgets.QLabel - login_label: QtWidgets.QLabel - password_label: QtWidgets.QLabel - url_input: QtWidgets.QComboBox - login_input: QtWidgets.QLineEdit - password_input: QtWidgets.QLineEdit - input_layout: QtWidgets.QFormLayout - login_button: QtWidgets.QPushButton - buttons_layout: QtWidgets.QHBoxLayout - main_widget: QtWidgets.QVBoxLayout + _module = None + _is_logged = False + url_label = None + login_label = None + password_label = None + url_input = None + login_input = None + password_input = None + input_layout = None + login_button = None + buttons_layout = None + main_widget = None - login_changed: QtCore.Signal = QtCore.Signal() + login_changed = QtCore.Signal() def __init__(self, module, parent=None): super(CredentialsDialog, self).__init__(parent) @@ -168,7 +167,7 @@ class CredentialsDialog(QtWidgets.QDialog): self._clear_shotgrid_login() self._on_logout() - def set_error(self, msg: str): + def set_error(self, msg): self.error_label.setText(msg) self.error_label.show() @@ -184,15 +183,15 @@ class CredentialsDialog(QtWidgets.QDialog): def _close_widget(self): self.hide() - def _valid_input(self, input_widget: QtWidgets.QLineEdit): + def _valid_input(self, input_widget): input_widget.setStyleSheet("") - def _invalid_input(self, input_widget: QtWidgets.QLineEdit): + def _invalid_input(self, input_widget): input_widget.setStyleSheet("border: 1px solid red;") def login_with_credentials( - self, url: str, login: str, password: str - ) -> bool: + self, url, login, password + ): verification = credentials.check_credentials(url, login, password) if verification: credentials.save_credentials(login, password, False) diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py index 0be58e3b20..4038d77b03 100644 --- a/openpype/modules/shotgrid/tray/shotgrid_tray.py +++ b/openpype/modules/shotgrid/tray/shotgrid_tray.py @@ -1,6 +1,5 @@ import os import webbrowser -from typing import Any from Qt import QtWidgets @@ -11,11 +10,11 @@ from openpype.modules.shotgrid.tray.credential_dialog import ( class ShotgridTrayWrapper: - module: Any - credentials_dialog: CredentialsDialog - logged_user_label: QtWidgets.QAction + module = None + credentials_dialog = None + logged_user_label = None - def __init__(self, module) -> None: + def __init__(self, module): self.module = module self.credentials_dialog = CredentialsDialog(module) self.credentials_dialog.login_changed.connect(self.set_login_label) @@ -65,7 +64,7 @@ class ShotgridTrayWrapper: ) m.addAction(shotgrid_manager_action) - def validate(self) -> bool: + def validate(self): login = credentials.get_local_login() if not login: diff --git a/poetry.lock b/poetry.lock index 7998ede693..4372f34ff7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.9.3" +version = "2.11.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.14" +wrapt = ">=1.11,<2" [[package]] name = "async-timeout" @@ -236,7 +236,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -295,7 +295,7 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -309,7 +309,7 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "36.0.1" +version = "36.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -346,9 +346,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "dill" +version = "0.3.4" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "dnspython" -version = "2.2.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = false @@ -364,7 +375,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -372,7 +383,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.26.0" +version = "11.29.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -397,7 +408,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.4.0" +version = "1.5.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -464,7 +475,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -476,7 +487,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.4.0" +version = "2.7.1" description = "Google API client core library" category = "main" optional = false @@ -495,7 +506,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] [[package]] name = "google-api-python-client" -version = "1.12.10" +version = "1.12.11" description = "Google API Client Library for Python" category = "main" optional = false @@ -511,7 +522,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.6.0" +version = "2.6.3" description = "Google Authentication Library" category = "main" optional = false @@ -543,7 +554,7 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.54.0" +version = "1.56.0" description = "Common protobufs used in Google APIs" category = "main" optional = false @@ -557,7 +568,7 @@ grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.20.2" +version = "0.20.4" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -584,7 +595,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.3" description = "Read metadata from Python packages" category = "main" optional = false @@ -595,9 +606,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -637,14 +648,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.7.1" +version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -722,11 +733,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mccabe" @@ -777,7 +788,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.10.1" +version = "2.10.3" description = "SSH2 protocol library" category = "main" optional = false @@ -809,7 +820,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -820,15 +831,19 @@ six = "*" [[package]] name = "pillow" -version = "9.0.0" +version = "9.1.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -871,11 +886,11 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.19.4" +version = "3.20.0" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [[package]] name = "py" @@ -966,21 +981,25 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.12.2" +version = "2.13.5" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.9.0,<2.10" +astroid = ">=2.11.2,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" +mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" -toml = ">=0.9.2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +[package.extras] +testutil = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "3.12.3" @@ -1031,7 +1050,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.2" +version = "8.4.1" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1039,39 +1058,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.2" +version = "8.4.1" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" -pyobjc-framework-Quartz = ">=8.2" +pyobjc-core = ">=8.4.1" +pyobjc-framework-Cocoa = ">=8.4.1" +pyobjc-framework-Quartz = ">=8.4.1" [[package]] name = "pyobjc-framework-cocoa" -version = "8.2" +version = "8.4.1" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" +pyobjc-core = ">=8.4.1" [[package]] name = "pyobjc-framework-quartz" -version = "8.2" +version = "8.4.1" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" +pyobjc-core = ">=8.4.1" +pyobjc-framework-Cocoa = ">=8.4.1" [[package]] name = "pyparsing" @@ -1175,7 +1194,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1287,6 +1306,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "shotgun-api3" +version = "3.3.3" +description = "Shotgun Python API" +category = "main" +optional = false +python-versions = "*" +develop = false + +[package.source] +type = "git" +url = "https://github.com/shotgunsoftware/python-api.git" +reference = "v3.3.3" +resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" + [[package]] name = "six" version = "1.16.0" @@ -1297,7 +1331,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.13.0" +version = "3.15.2" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1305,7 +1339,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==2.0.1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.1.0)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1325,7 +1359,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.2" +version = "2.1.3" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1333,18 +1367,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.3" +version = "4.5.0" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1352,19 +1387,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.3" +version = "0.4" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1374,16 +1409,22 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" +[package.extras] +dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] -sphinx = "*" +docutils = "<0.18" +sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1504,7 +1545,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false @@ -1520,7 +1561,7 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.0.1" +version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false @@ -1536,14 +1577,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1568,7 +1609,7 @@ six = "*" [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.0" description = "Module for decorators, wrappers and monkey patching." category = "dev" optional = false @@ -1606,20 +1647,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "b02313c8255a1897b0f0617ad4884a5943696c363512921aab1cb2dd8f4fdbe0" +content-hash = "5df45b7a0d0505ca0db8e62f5a4e945601c53908c54ab752d5362552596bd69c" [metadata.files] acre = [] @@ -1722,8 +1763,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, + {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"}, + {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1830,8 +1871,8 @@ chardet = [ {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1854,69 +1895,69 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"}, + {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"}, + {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, + {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -1946,25 +1987,29 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] +dill = [ + {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"}, + {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"}, +] dnspython = [ - {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, - {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] dropbox = [ - {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, - {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, - {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, + {file = "dropbox-11.29.0-py2-none-any.whl", hash = "sha256:2200ad5f42e00ae00d45db4a050fa199fe701ddc979fd1396d2c3e8912476c60"}, + {file = "dropbox-11.29.0-py3-none-any.whl", hash = "sha256:bf81a822e662bd337f4cd33fe39580c0b6ee4781d018ef1b31dcef2f402986f2"}, + {file = "dropbox-11.29.0.tar.gz", hash = "sha256:09b59f962ac28ce5b80d5f870c00c5fe7a637c4ac8d095c7c72fdab1e07376fc"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, + {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2043,32 +2088,32 @@ gitdb = [ {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] google-api-core = [ - {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, - {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, + {file = "google-api-core-2.7.1.tar.gz", hash = "sha256:b0fa577e512f0c8e063386b974718b8614586a798c5894ed34bedf256d9dae24"}, + {file = "google_api_core-2.7.1-py3-none-any.whl", hash = "sha256:6be1fc59e2a7ba9f66808bbc22f976f81e4c3e7ab20fa0620ce42686288787d0"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, - {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, + {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, + {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] google-auth = [ - {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, - {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, + {file = "google-auth-2.6.3.tar.gz", hash = "sha256:d65bb0e3701eaaa64fd2aa85e1325580524b0bddc6dc5db3ab89c481b6a20141"}, + {file = "google_auth-2.6.3-py2.py3-none-any.whl", hash = "sha256:5e079eb4d21df1853d55cf2b6766b77ef36f7f7bdaf7d4a70434aa97c7578d60"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, - {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, + {file = "googleapis-common-protos-1.56.0.tar.gz", hash = "sha256:4007500795bcfc269d279f0f7d253ae18d6dc1ff5d5a73613ffe452038b1ec5f"}, + {file = "googleapis_common_protos-1.56.0-py2.py3-none-any.whl", hash = "sha256:60220c89b8bd5272159bed4929ecdc1243ae1f73437883a499a44a1cbc084086"}, ] httplib2 = [ - {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, - {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, + {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, + {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -2079,8 +2124,8 @@ imagesize = [ {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2095,8 +2140,8 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, - {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -2157,75 +2202,46 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2298,54 +2314,60 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, - {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, + {file = "paramiko-2.10.3-py2.py3-none-any.whl", hash = "sha256:ac6593479f2b47a9422eca076b22cff9f795495e6733a64723efc75dd8c92101"}, + {file = "paramiko-2.10.3.tar.gz", hash = "sha256:ddb1977853aef82804b35d72a0e597b244fa326c404c350bd00c5b01dbfee71a"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pillow = [ - {file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"}, - {file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"}, - {file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"}, - {file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"}, - {file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"}, - {file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"}, - {file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"}, - {file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"}, - {file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"}, - {file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"}, - {file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"}, - {file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"}, - {file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"}, + {file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"}, + {file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"}, + {file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"}, + {file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"}, + {file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"}, + {file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"}, + {file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"}, + {file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"}, + {file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"}, + {file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"}, + {file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"}, + {file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"}, + {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2360,32 +2382,30 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, - {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, - {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, - {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, - {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, - {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, - {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, - {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, - {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, - {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, - {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, - {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, - {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, - {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, - {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, - {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, - {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, - {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, - {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, - {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, - {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, - {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, - {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, - {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, - {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, - {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, + {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, + {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, + {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, + {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, + {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, + {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, + {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, + {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, + {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, + {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, + {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, + {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, + {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, + {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, + {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, + {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2450,8 +2470,8 @@ pygments = [ {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, + {file = "pylint-2.13.5-py3-none-any.whl", hash = "sha256:c149694cfdeaee1aa2465e6eaab84c87a881a7d55e6e93e09466be7164764d1e"}, + {file = "pylint-2.13.5.tar.gz", hash = "sha256:dab221658368c7a05242e673c275c488670144123f4bd262b2777249c1c0de9b"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2580,40 +2600,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, - {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, - {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, - {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, - {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, + {file = "pyobjc-core-8.4.1.tar.gz", hash = "sha256:df98669e957adb33566d9ef46773a5ac876a81afe8849c282d6a80448e35dd74"}, + {file = "pyobjc_core-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a89cac910fbd64728fe7ec0c7a3a7cf20959bc1d7e2f41db4d7800556e47745"}, + {file = "pyobjc_core-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2cf1d4348cb99fcba04fa38199a46e35263b2fe7bb66e6dfbd4f19bc2602998d"}, + {file = "pyobjc_core-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a130324b25c0f5f4cfe30b6a28b8e70865d6e1eee158caababb603906ef431d2"}, + {file = "pyobjc_core-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31195d1a8f00da99abf79643f902d09c709dbbe9c9b6feb6b585303c57d720c"}, + {file = "pyobjc_core-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e7fd2aefb53a96a8f688c8bb36c6ebd5446250a7251bfa6b688a045e05afc60b"}, + {file = "pyobjc_core-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3191173ce268e23c84d84f036fc94c3a8749a6726fc7fe73baf27dbac14f7d0"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, + {file = "pyobjc-framework-ApplicationServices-8.4.1.tar.gz", hash = "sha256:b058466266412d2bf24b0303785c91538961367f26db616bf2f9f45c498a83a3"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:362d178c624a617fc60c3a35397550193d82da9a59272b215cf1dc6fb68c011c"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:41f0f6be6d343f91de92cab053be4a983e3936b364ca267a94c6e06bc34ff7fe"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:73287b758a70a037b6e74a60036f4adb1677407dfeaddee94102074d92539e6e"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:287b541b66c5c931a11dde90d7be69ddd2e5c3624c2e980e679f5242ec3ee0da"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:780fbe2e3b02237f79e2f5780094dd95b7308ecdb265c062b78a507b9564eb4d"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:092c4835e73e5dab1f3c498511b28b2e96503671fc7c1bc25f732c5e687b7214"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, - {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, - {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, - {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, - {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, + {file = "pyobjc-framework-Cocoa-8.4.1.tar.gz", hash = "sha256:dc596bac0f5d424f67944e95b2d0d7c94a07c4166359d7b4a4d4ae4f8e112822"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cfbe038a0108ae1b45f8f7067af70af5811b8352d2dc3d86a7bcb4484ff5d56e"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:118225562064d991bafb41d0913899d6b3d723984d1888cb7181e4dba63b22c2"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d117a1eb24fd317e9f63792ac6a8703ed899de5d42e8a861c7bf885625668c31"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3af8577acbd6b980d3b9270ec99bc0164f66ef8397351a72fcdee527f23c1a34"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91fdc964acb4dee4d37ae81fb603d48397739dbbfcc1eadbe0cdafaa8144b6e6"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:197dd28668e786b55d7d4139afd85c285f780564ebbccc166e84a24be31de34f"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, - {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, - {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, - {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, - {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, + {file = "pyobjc-framework-Quartz-8.4.1.tar.gz", hash = "sha256:f8acebf0b526f0687e79ea6946e8f2528ced4ef02d0ed3fbf7116124b2749e52"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a82d9eab3b2a6fca46602465f731815026d06e4fd0ba65b5b5211f1a0472b861"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bd0a506385141b81330464b6e2537a7056cdca56deb98222b6926a04f72a3e6"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:64cc616386b7a387dce53d3adb8c12a8461c2720861ab36aeeb53768cd0ba7e4"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a2dff998c4d1286998390f58a3131b99bdc9dbf5c3d881562b33f168098bbe2e"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a98c58e1b265d5b413f5ecc6ec186b9e305fb6e37909d8b4b97fd681176f5f1c"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39f55426ef883cbe12a53969f90886f71e2a94513c98cf3730efdf404cdc5c83"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2646,8 +2666,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2697,13 +2717,14 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, - {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, + {file = "slack_sdk-3.15.2-py2.py3-none-any.whl", hash = "sha256:e1fa26786169176e707676decc287fd9d3d547bbc43c0a1a4f99eb373b07da94"}, + {file = "slack_sdk-3.15.2.tar.gz", hash = "sha256:128f3bb0b5b91454a3d5f140a61f3db370a0e1b50ffe0a8d9e9ebe0e894faed7"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2714,20 +2735,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, - {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, + {file = "speedcopy-2.1.3-py3-none-any.whl", hash = "sha256:ecbd71b7fcd2a01ce444268fb50db6bf843845aa494018571b6726f5b37c7869"}, + {file = "speedcopy-2.1.3.tar.gz", hash = "sha256:8e14a2b94352b91229c2c0712ea39ed265ce9ec780f899e43e9f2cf5435cf959"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, - {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2770,8 +2791,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, @@ -2800,16 +2821,16 @@ typed-ast = [ {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2820,57 +2841,70 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, + {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, + {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, + {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, + {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, + {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, + {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, + {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, + {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, + {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, + {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, + {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, + {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, + {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, + {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, + {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -2951,6 +2985,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From 3a3e552b0aeb353a1e74144993faab84b0a2c395 Mon Sep 17 00:00:00 2001 From: BenoitConnan Date: Fri, 8 Apr 2022 16:28:28 +0200 Subject: [PATCH 009/258] Make the publish work under maya 2018 --- openpype/modules/shotgrid/aop/patch.py | 15 +++++++++------ openpype/modules/shotgrid/lib/__init__.py | 0 openpype/modules/shotgrid/lib/settings.py | 6 +++--- openpype/modules/shotgrid/shotgrid_module.py | 13 +++++++------ 4 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 openpype/modules/shotgrid/lib/__init__.py diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 208ca715d3..580aa2341f 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -1,6 +1,5 @@ from copy import copy -from avalon.api import AvalonMongoDB from openpype.api import Logger from openpype.modules.shotgrid.lib import ( @@ -42,8 +41,12 @@ def _fetch_linked_project_names(): def patch_avalon_db(): _LOG.debug("Run avalon patching") - if AvalonMongoDB.projects is _patched_projects: - return None - _LOG.debug("Patch Avalon.projects method") - AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) - AvalonMongoDB.projects = _patched_projects + try: + from avalon.api import AvalonMongoDB + if AvalonMongoDB.projects is _patched_projects: + return None + _LOG.debug("Patch Avalon.projects method") + AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) + AvalonMongoDB.projects = _patched_projects + except e: + _LOG.error("Unable to patch avalon db "+ e) diff --git a/openpype/modules/shotgrid/lib/__init__.py b/openpype/modules/shotgrid/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 954b96f3c2..4c21501b99 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,5 +1,5 @@ import os -from functools import lru_cache +# from functools import lru_cache from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings @@ -13,12 +13,12 @@ def get_project_list(): return db.list_collection_names() -@lru_cache(maxsize=64) +# @lru_cache(maxsize=64) def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@lru_cache(maxsize=64) +# @lru_cache(maxsize=64) def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 1f05ae5a52..009f0028f0 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -8,10 +8,6 @@ from openpype_interfaces import ( ) from openpype.modules import OpenPypeModule -from .aop.patch import patch_avalon_db -from .tray.shotgrid_tray import ( - ShotgridTrayWrapper, -) SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -26,8 +22,6 @@ class ShotgridModule( tray_wrapper = None def initialize(self, modules_settings): - patch_avalon_db() - threading.Timer(10.0, patch_avalon_db).start() shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) self.leecher_manager_url = shotgrid_settings.get( @@ -51,8 +45,15 @@ class ShotgridModule( return os.path.join(SHOTGRID_MODULE_DIR, "hooks") def tray_init(self): + from .tray.shotgrid_tray import ShotgridTrayWrapper + from .aop.patch import patch_avalon_db + + patch_avalon_db() + threading.Timer(10.0, patch_avalon_db).start() + self.tray_wrapper = ShotgridTrayWrapper(self) + def tray_start(self): return self.tray_wrapper.validate() From 0c85093937829c0c626456a00a30d0a23c8cbcfd Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Mon, 11 Apr 2022 10:35:39 +0200 Subject: [PATCH 010/258] Add memoize implementation for settings purposes --- openpype/modules/shotgrid/lib/settings.py | 6 +++--- openpype/modules/shotgrid/lib/tools.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 openpype/modules/shotgrid/lib/tools.py diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4c21501b99..4a772de5b7 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,9 +1,9 @@ import os -# from functools import lru_cache from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME +from openpype.modules.shotgrid.lib.tools import memoize def get_project_list(): @@ -13,12 +13,12 @@ def get_project_list(): return db.list_collection_names() -# @lru_cache(maxsize=64) +@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -# @lru_cache(maxsize=64) +@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/lib/tools.py b/openpype/modules/shotgrid/lib/tools.py new file mode 100644 index 0000000000..6305e9ca50 --- /dev/null +++ b/openpype/modules/shotgrid/lib/tools.py @@ -0,0 +1,16 @@ +from functools import wraps + + +def memoize(function): + memo = {} + + @wraps(function) + def wrapper(*args): + try: + return memo[args] + except KeyError: + rv = function(*args) + memo[args] = rv + return rv + + return wrapper From 16dc4337ad467629b7ea9d984d616e33f2dd5c39 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Mon, 11 Apr 2022 10:39:10 +0200 Subject: [PATCH 011/258] Fix after hound notes --- openpype/modules/shotgrid/aop/patch.py | 4 ++-- openpype/modules/shotgrid/shotgrid_module.py | 15 +++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 580aa2341f..56477f3bf9 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -48,5 +48,5 @@ def patch_avalon_db(): _LOG.debug("Patch Avalon.projects method") AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) AvalonMongoDB.projects = _patched_projects - except e: - _LOG.error("Unable to patch avalon db "+ e) + except Exception as e: + _LOG.error("Unable to patch avalon db {}".format(e)) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 009f0028f0..06a07cc4f7 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -12,9 +12,7 @@ from openpype.modules import OpenPypeModule SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class ShotgridModule( - OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths -): +class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths): leecher_manager_url = None name = "shotgrid" enabled = False @@ -24,9 +22,7 @@ class ShotgridModule( def initialize(self, modules_settings): shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get( - "leecher_manager_url", "" - ) + self.leecher_manager_url = shotgrid_settings.get("leecher_manager_url", "") def connect_with_modules(self, enabled_modules): pass @@ -35,11 +31,7 @@ class ShotgridModule( return {"PROJECT_ID": self.project_id} def get_plugin_paths(self): - return { - "publish": [ - os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") - ] - } + return {"publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")]} def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") @@ -53,7 +45,6 @@ class ShotgridModule( self.tray_wrapper = ShotgridTrayWrapper(self) - def tray_start(self): return self.tray_wrapper.validate() From 57386db03c9980c107488def4ad7a8155ede52a2 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Mon, 11 Apr 2022 10:45:02 +0200 Subject: [PATCH 012/258] Fix after hound notes, part 2 --- openpype/modules/shotgrid/shotgrid_module.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 06a07cc4f7..6e0cbe987b 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -12,7 +12,9 @@ from openpype.modules import OpenPypeModule SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths): +class ShotgridModule( + OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths +): leecher_manager_url = None name = "shotgrid" enabled = False @@ -22,7 +24,9 @@ class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths def initialize(self, modules_settings): shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get("leecher_manager_url", "") + self.leecher_manager_url = shotgrid_settings.get( + "leecher_manager_url", "" + ) def connect_with_modules(self, enabled_modules): pass @@ -31,7 +35,11 @@ class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths return {"PROJECT_ID": self.project_id} def get_plugin_paths(self): - return {"publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")]} + return { + "publish": [ + os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") + ] + } def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") From 590ece0ed898b7265e5198b82f54be1e5741cd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 2 May 2022 18:11:35 +0200 Subject: [PATCH 013/258] fix image prefix warning --- .../maya/plugins/publish/validate_rendersettings.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 023e27de17..dc2a9c5d1c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -92,6 +92,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): def get_invalid(cls, instance): invalid = False + multipart = False renderer = instance.data['renderer'] layer = instance.data['setMembers'] @@ -111,6 +112,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "{aov_separator}", instance.data.get("aovSeparator", "_")) required_prefix = "maya/" + default_prefix = cls.ImagePrefixTokens[renderer] if not anim_override: invalid = True @@ -211,14 +213,20 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Wrong image prefix [ {} ] - " "You can't use '' token " "with merge AOVs turned on".format(prefix)) + default_prefix = re.sub( + cls.R_AOV_TOKEN, "", default_prefix) + # remove aov token from prefix to pass validation + # first resolve it and then remove if dangling + default_prefix = default_prefix.replace( + "{aov_separator}", instance.data.get("aovSeparator", "_")) + default_prefix = default_prefix.rstrip( + instance.data.get("aovSeparator", "_")) elif not re.search(cls.R_AOV_TOKEN, prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " "doesn't have: '' or " "token".format(prefix)) - # prefix check - default_prefix = cls.ImagePrefixTokens[renderer] default_prefix = default_prefix.replace( "{aov_separator}", instance.data.get("aovSeparator", "_")) if prefix.lower() != default_prefix.lower(): From d636a4144dd4cfff4517dd6b39f6d9524160ed0d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:35:06 +0300 Subject: [PATCH 014/258] Replace plugin name with more descriptive one. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- openpype/settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index ae29f8b95b..84cdd186dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -15,7 +15,7 @@ from openpype.lib import ( import shutil -class ExtractJpegEXR(pyblish.api.InstancePlugin): +class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" label = "Extract Jpeg EXR" diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 7b223798f1..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -33,7 +33,7 @@ "enabled": false, "profiles": [] }, - "ExtractJpegEXR": { + "ExtractThumbnail": { "enabled": true, "ffmpeg_args": { "input": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 061874e31c..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -126,8 +126,8 @@ "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", "is_group": true, "children": [ { From 78c70819156fc0ad6896619abc8b4d2cc017e589 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:38:04 +0300 Subject: [PATCH 015/258] Change label name. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 84cdd186dc..11dfca8eb2 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -18,7 +18,7 @@ import shutil class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" - label = "Extract Jpeg EXR" + label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", From 55cdecd95137a197664c9ed6307d746d2d7e95b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:24:00 +0200 Subject: [PATCH 016/258] Implement support for Redshift Proxy export in Houdini Tested with Houdini 19.0.589 + Redshift 3.5.01 --- openpype/hosts/houdini/api/lib.py | 2 + .../plugins/create/create_redshift_proxy.py | 49 +++++++++++++++++++ .../houdini/plugins/publish/collect_frames.py | 2 +- .../plugins/publish/collect_output_node.py | 3 ++ .../plugins/publish/extract_redshift_proxy.py | 48 ++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_redshift_proxy.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 603519069a..96ca019f8f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -130,6 +130,8 @@ def get_output_parameter(node): elif node_type == "arnold": if node.evalParm("ar_ass_export_enable"): return node.parm("ar_ass_file") + elif node_type == "Redshift_Proxy_Output": + return node.parm("RS_archive_file") raise TypeError("Node type '%s' not supported" % node_type) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py new file mode 100644 index 0000000000..52c81240fa --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -0,0 +1,49 @@ +import hou +from openpype.hosts.houdini.api import plugin + + +class CreateRedshiftProxy(plugin.Creator): + """Redshift Proxy""" + + label = "Redshift Proxy" + family = "redshiftproxy" + icon = "magic" + + def __init__(self, *args, **kwargs): + super(CreateRedshiftProxy, self).__init__(*args, **kwargs) + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + # Redshift provides a `Redshift_Proxy_Output` node type which shows + # a limited set of parameters by default and is set to extract a + # Redshift Proxy. However when "imprinting" extra parameters needed + # for OpenPype it starts showing all its parameters again. It's unclear + # why this happens. + # TODO: Somehow enforce so that it only shows the original limited + # attributes of the Redshift_Proxy_Output node type + self.data.update({"node_type": "Redshift_Proxy_Output"}) + + def _process(self, instance): + """Creator main entry point. + + Args: + instance (hou.Node): Created Houdini instance. + + """ + parms = { + "RS_archive_file": '$HIP/pyblish/`chs("subset")`.$F4.rs', + } + + if self.nodes: + node = self.nodes[0] + path = node.path() + parms["RS_archive_sopPath"] = path + + instance.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index fac40b4d2b..9bd43d8a09 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -20,7 +20,7 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["vdbcache", "imagesequence", "ass"] + families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py index 938ee81cc3..0130c0a8da 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/collect_output_node.py @@ -12,6 +12,7 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): "imagesequence", "usd", "usdrender", + "redshiftproxy" ] hosts = ["houdini"] @@ -54,6 +55,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): else: out_node = node.parm("loppath").evalAsNode() + elif node_type == "Redshift_Proxy_Output": + out_node = node.parm("RS_archive_sopPath").evalAsNode() else: raise ValueError( "ROP node type '%s' is" " not supported." % node_type diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py new file mode 100644 index 0000000000..eb7e0d5677 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -0,0 +1,48 @@ +import os + +import pyblish.api +import openpype.api +from openpype.hosts.houdini.api.lib import render_rop + + +class ExtractRedshiftProxy(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract Redshift Proxy" + families = ["redshiftproxy"] + hosts = ["houdini"] + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("RS_archive_file") + staging_dir = os.path.normpath(os.path.dirname(output)) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + self.log.info("Writing Redshift Proxy '%s' to '%s'" % (file_name, + staging_dir)) + + render_rop(ropnode) + + output = instance.data["frames"] + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "rs", + "ext": "rs", + "files": output, + "stagingDir": staging_dir, + } + + # A single frame may also be rendered without start/end frame. + if "frameStart" in instance.data and "frameEnd" in instance.data: + representation["frameStart"] = instance.data["frameStart"] + representation["frameEnd"] = instance.data["frameEnd"] + + instance.data["representations"].append(representation) \ No newline at end of file From be1453b4e7971ad3f20f87de0167865b38746340 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:27:40 +0200 Subject: [PATCH 017/258] Fix new line --- .../hosts/houdini/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py index eb7e0d5677..c754d60c59 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -45,4 +45,4 @@ class ExtractRedshiftProxy(openpype.api.Extractor): representation["frameStart"] = instance.data["frameStart"] representation["frameEnd"] = instance.data["frameEnd"] - instance.data["representations"].append(representation) \ No newline at end of file + instance.data["representations"].append(representation) From 0f84e7d92a661f99e347bedb110928f014b37eec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:28:40 +0200 Subject: [PATCH 018/258] Remove unused import --- openpype/hosts/houdini/plugins/create/create_redshift_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py index 52c81240fa..da4d80bf2b 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -1,4 +1,3 @@ -import hou from openpype.hosts.houdini.api import plugin From 606ef6415d229e7ee29f2776749fc33b288e0dd8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:42:55 +0200 Subject: [PATCH 019/258] Fix popping of `handles` --- openpype/hosts/maya/plugins/create/create_yeti_cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_yeti_cache.py b/openpype/hosts/maya/plugins/create/create_yeti_cache.py index 86e13b95b2..e8c3203f21 100644 --- a/openpype/hosts/maya/plugins/create/create_yeti_cache.py +++ b/openpype/hosts/maya/plugins/create/create_yeti_cache.py @@ -22,7 +22,8 @@ class CreateYetiCache(plugin.Creator): # Add animation data without step and handles anim_data = lib.collect_animation_data() anim_data.pop("step") - anim_data.pop("handles") + anim_data.pop("handleStart") + anim_data.pop("handleEnd") self.data.update(anim_data) # Add samples From e4d54aaa7a8304b426b67b707565451ce5e5599d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:44:27 +0200 Subject: [PATCH 020/258] Fix invalid refactored usage --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index d12567a55a..6b5054a198 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -167,7 +167,7 @@ class ExtractYetiRig(openpype.api.Extractor): resources = instance.data.get("resources", {}) with disconnect_plugs(settings, members): with yetigraph_attribute_values(resources_dir, resources): - with maya.attribute_values(attr_value): + with lib.attribute_values(attr_value): cmds.select(nodes, noExpand=True) cmds.file(maya_path, force=True, From 0f97c9f3d388a8973dce13e68fb9d263c0441b48 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:50:41 +0200 Subject: [PATCH 021/258] Time values are required for exporting without errors --- openpype/hosts/maya/plugins/publish/extract_yeti_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index 0d85708789..b0a60b77f4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -29,9 +29,9 @@ class ExtractYetiCache(openpype.api.Extractor): data_file = os.path.join(dirname, "yeti.fursettings") # Collect information for writing cache - start_frame = instance.data.get("frameStartHandle") - end_frame = instance.data.get("frameEndHandle") - preroll = instance.data.get("preroll") + start_frame = instance.data["frameStartHandle"] + end_frame = instance.data["frameEndHandle"] + preroll = instance.data["preroll"] if preroll > 0: start_frame -= preroll From 6fb9bb1558401273e2437e3e50a18877c296851e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:54:41 +0200 Subject: [PATCH 022/258] Allow empty input_SET --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 6b5054a198..f981c4fe50 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -157,7 +157,7 @@ class ExtractYetiRig(openpype.api.Extractor): input_set = next(i for i in instance if i == "input_SET") # Get all items - set_members = cmds.sets(input_set, query=True) + set_members = cmds.sets(input_set, query=True) or [] set_members += cmds.listRelatives(set_members, allDescendents=True, fullPath=True) or [] From 2a2dbd243408f4eed8863fd86967a39080b4931d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:56:45 +0200 Subject: [PATCH 023/258] Force required frame values for a single frame cache extract for `yetiRig` family. --- .../hosts/maya/plugins/publish/collect_yeti_rig.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py index 029432223b..bc15edd9e0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py @@ -43,11 +43,12 @@ class CollectYetiRig(pyblish.api.InstancePlugin): instance.data["resources"] = yeti_resources - # Force frame range for export - instance.data["frameStart"] = cmds.playbackOptions( - query=True, animationStartTime=True) - instance.data["frameEnd"] = cmds.playbackOptions( - query=True, animationStartTime=True) + # Force frame range for yeti cache export for the rig + start = cmds.playbackOptions(query=True, animationStartTime=True) + for key in ["frameStart", "frameEnd", + "frameStartHandle", "frameEndHandle"]: + instance.data[key] = start + instance.data["preroll"] = 0 def collect_input_connections(self, instance): """Collect the inputs for all nodes in the input_SET""" From 8f7428dd95d7fe461151821f901fa3059ff5309a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 18 May 2022 15:07:53 +0300 Subject: [PATCH 024/258] Add OIIO --- openpype/plugins/publish/extract_jpeg_exr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 11dfca8eb2..2d3ad1e8a8 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,6 +3,7 @@ import os import pyblish.api from openpype.lib import ( get_ffmpeg_tool_path, + get_oiio_tools_path, run_subprocess, path_to_subprocess_arg, @@ -29,6 +30,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None + oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) @@ -119,7 +121,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform From feb07912c50728054a5078c23a870f445969f947 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:27:50 +0200 Subject: [PATCH 025/258] Fix yeti publish and load for caches --- .../maya/plugins/load/load_yeti_cache.py | 244 +++++++++--------- .../plugins/publish/extract_yeti_cache.py | 38 +-- .../maya/plugins/publish/extract_yeti_rig.py | 4 +- 3 files changed, 146 insertions(+), 140 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index fb903785ae..9752188551 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -1,15 +1,13 @@ import os import json import re -import glob from collections import defaultdict -from pprint import pprint +import clique from maya import cmds from openpype.api import get_project_settings from openpype.pipeline import ( - legacy_io, load, get_representation_path ) @@ -17,7 +15,15 @@ from openpype.hosts.maya.api import lib from openpype.hosts.maya.api.pipeline import containerise +def set_attribute(node, attr, value): + """Wrapper of set attribute which ignores None values""" + if value is None: + return + lib.set_attribute(node, attr, value) + + class YetiCacheLoader(load.LoaderPlugin): + """Load Yeti Cache with one or more Yeti nodes""" families = ["yeticache", "yetiRig"] representations = ["fur"] @@ -28,6 +34,16 @@ class YetiCacheLoader(load.LoaderPlugin): color = "orange" def load(self, context, name=None, namespace=None, data=None): + """Loads a .fursettings file defining how to load .fur sequences + + A single yeticache or yetiRig can have more than a single pgYetiMaya + nodes and thus load more than a single yeti.fur sequence. + + The .fursettings file defines what the node names should be and also + what "cbId" attribute they should receive to match the original source + and allow published looks to also work for Yeti rigs and its caches. + + """ try: family = context["representation"]["context"]["family"] @@ -43,22 +59,11 @@ class YetiCacheLoader(load.LoaderPlugin): if not cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): cmds.loadPlugin("pgYetiMaya", quiet=True) - # Get JSON - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine fursettings file path') - settings_fname = "{}.fursettings".format(fbase.group(1)) - with open(settings_fname, "r") as fp: - fursettings = json.load(fp) - - # Check if resources map exists - # Get node name from JSON - if "nodes" not in fursettings: - raise RuntimeError("Encountered invalid data, expect 'nodes' in " - "fursettings.") - - node_data = fursettings["nodes"] - nodes = self.create_nodes(namespace, node_data) + # Create Yeti cache nodes according to settings + settings = self.read_settings(self.fname) + nodes = [] + for node in settings["nodes"]: + nodes.extend(self.create_node(namespace, node)) group_name = "{}:{}".format(namespace, name) group_node = cmds.group(nodes, name=group_name) @@ -111,28 +116,14 @@ class YetiCacheLoader(load.LoaderPlugin): def update(self, container, representation): - legacy_io.install() namespace = container["namespace"] container_node = container["objectName"] - fur_settings = legacy_io.find_one( - {"parent": representation["parent"], "name": "fursettings"} - ) - - pprint({"parent": representation["parent"], "name": "fursettings"}) - pprint(fur_settings) - assert fur_settings is not None, ( - "cannot find fursettings representation" - ) - - settings_fname = get_representation_path(fur_settings) path = get_representation_path(representation) - # Get all node data - with open(settings_fname, "r") as fp: - settings = json.load(fp) + settings = self.read_settings(path) # Collect scene information of asset - set_members = cmds.sets(container["objectName"], query=True) + set_members = lib.get_container_members(container) container_root = lib.get_container_transforms(container, members=set_members, root=True) @@ -147,7 +138,7 @@ class YetiCacheLoader(load.LoaderPlugin): # Re-assemble metadata with cbId as keys meta_data_lookup = {n["cbId"]: n for n in settings["nodes"]} - # Compare look ups and get the nodes which ar not relevant any more + # Delete nodes by "cbId" that are not in the updated version to_delete_lookup = {cb_id for cb_id in scene_lookup.keys() if cb_id not in meta_data_lookup} if to_delete_lookup: @@ -163,25 +154,18 @@ class YetiCacheLoader(load.LoaderPlugin): fullPath=True) or [] to_remove.extend(shapes + transforms) - # Remove id from look uop + # Remove id from lookup scene_lookup.pop(_id, None) cmds.delete(to_remove) - # replace frame in filename with %04d - RE_frame = re.compile(r"(\d+)(\.fur)$") - file_name = re.sub(RE_frame, r"%04d\g<2>", os.path.basename(path)) - for cb_id, data in meta_data_lookup.items(): - - # Update cache file name - data["attrs"]["cacheFileName"] = os.path.join( - os.path.dirname(path), file_name) + for cb_id, node_settings in meta_data_lookup.items(): if cb_id not in scene_lookup: - + # Create new nodes self.log.info("Creating new nodes ..") - new_nodes = self.create_nodes(namespace, [data]) + new_nodes = self.create_node(namespace, node_settings) cmds.sets(new_nodes, addElement=container_node) cmds.parent(new_nodes, container_root) @@ -218,14 +202,8 @@ class YetiCacheLoader(load.LoaderPlugin): children=True) yeti_node = yeti_nodes[0] - for attr, value in data["attrs"].items(): - # handle empty attribute strings. Those are reported - # as None, so their type is NoneType and this is not - # supported on attributes in Maya. We change it to - # empty string. - if value is None: - value = "" - lib.set_attribute(attr, value, yeti_node) + for attr, value in node_settings["attrs"].items(): + set_attribute(attr, value, yeti_node) cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), @@ -235,7 +213,6 @@ class YetiCacheLoader(load.LoaderPlugin): self.update(container, representation) # helper functions - def create_namespace(self, asset): """Create a unique namespace Args: @@ -253,100 +230,129 @@ class YetiCacheLoader(load.LoaderPlugin): return namespace - def validate_cache(self, filename, pattern="%04d"): - """Check if the cache has more than 1 frame + def get_cache_node_filepath(self, root, node_name): + """Get the cache file path for one of the yeti nodes. - All caches with more than 1 frame need to be called with `%04d` - If the cache has only one frame we return that file name as we assume + All caches with more than 1 frame need cache file name set with `%04d` + If the cache has only one frame we return the file name as we assume it is a snapshot. + This expects the files to be named after the "node name" through + exports with in Yeti. + Args: - filename(str) - pattern(str) + root(str): Folder containing cache files to search in. + node_name(str): Node name to search cache files for Returns: - str + str: Cache file path value needed for cacheFileName attribute """ - glob_pattern = filename.replace(pattern, "*") + name = node_name.replace(":", "_") + pattern = r"^({name})(\.[0-4]+)?(\.fur)$".format(name=re.escape(name)) - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] + files = [fname for fname in os.listdir(root) if re.match(pattern, + fname)] + if not files: + self.log.error("Could not find cache files for '{}' " + "with pattern {}".format(node_name, pattern)) + return if len(files) == 1: - return files[0] - elif len(files) == 0: - self.log.error("Could not find cache files for '%s'" % filename) + # Single file + return os.path.join(root, files[0]) - return filename + # Get filename for the sequence with padding + collections, remainder = clique.assemble(files) + assert not remainder, "This is a bug" + assert len(collections) == 1, "This is a bug" + collection = collections[0] - def create_nodes(self, namespace, settings): + # Assume padding from the first frame since clique returns 0 if the + # sequence contains no files padded with a zero at the start (e.g. + # a sequence starting at 1001) + padding = len(str(collection.indexes[0])) + + fname = "{head}%0{padding}d{tail}".format(collection.head, + padding, + collection.tail) + + return os.path.join(root, fname) + + def create_node(self, namespace, node_settings): """Create nodes with the correct namespace and settings Args: namespace(str): namespace - settings(list): list of dictionaries + node_settings(dict): Single "nodes" entry from .fursettings file. Returns: - list + list: Created nodes """ - nodes = [] - for node_settings in settings: - # Create pgYetiMaya node - original_node = node_settings["name"] - node_name = "{}:{}".format(namespace, original_node) - yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + # Get original names and ids + orig_transform_name = node_settings["transform"]["name"] + orig_shape_name = node_settings["name"] - # Create transform node - transform_node = node_name.rstrip("Shape") + # Add namespace + transform_name = "{}:{}".format(namespace, orig_transform_name) + shape_name = "{}:{}".format(namespace, orig_shape_name) - lib.set_id(transform_node, node_settings["transform"]["cbId"]) - lib.set_id(yeti_node, node_settings["cbId"]) + # Create pgYetiMaya node + transform_node = cmds.createNode("transform", + name=transform_name) + yeti_node = cmds.createNode("pgYetiMaya", + name=shape_name, + parent=transform_node) - nodes.extend([transform_node, yeti_node]) + lib.set_id(transform_node, node_settings["transform"]["cbId"]) + lib.set_id(yeti_node, node_settings["cbId"]) - # Ensure the node has no namespace identifiers - attributes = node_settings["attrs"] + nodes.extend([transform_node, yeti_node]) - # Check if cache file name is stored + # Update attributes with defaults + attributes = node_settings["attrs"] + attributes.update({ + "viewportDensity": 0.1, + "verbosity": 2, + "fileMode": 1, - # get number of # in path and convert it to C prinf format - # like %04d expected by Yeti - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine file path') - padding = len(fbase.group(2)) - if "cacheFileName" not in attributes: - cache = "{}.%0{}d.fur".format(fbase.group(1), padding) + # Fix render stats, like Yeti's own + # ../scripts/pgYetiNode.mel script + "visibleInReflections": True, + "visibleInRefractions": True + }) - self.validate_cache(cache) - attributes["cacheFileName"] = cache + # Apply attributes to pgYetiMaya node + for attr, value in attributes.items(): + set_attribute(attr, value, yeti_node) - # Update attributes with requirements - attributes.update({"viewportDensity": 0.1, - "verbosity": 2, - "fileMode": 1}) - - # Apply attributes to pgYetiMaya node - for attr, value in attributes.items(): - if value is None: - continue - lib.set_attribute(attr, value, yeti_node) - - # Fix for : YETI-6 - # Fixes the render stats (this is literally taken from Perigrene's - # ../scripts/pgYetiNode.mel script) - cmds.setAttr("{}.visibleInReflections".format(yeti_node), True) - cmds.setAttr("{}.visibleInRefractions".format(yeti_node), True) - - # Connect to the time node - cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + # Connect to the time node + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) return nodes + + def read_settings(self, path): + """Read .fursettings file and compute some additional attributes""" + + with open(path, "r") as fp: + fur_settings = json.load(fp) + + if "nodes" not in fur_settings: + raise RuntimeError("Encountered invalid data, " + "expected 'nodes' in fursettings.") + + # Compute the cache file name values we want to set for the nodes + root = os.path.dirname(path) + for node in fur_settings["nodes"]: + cache_filename = self.get_cache_node_filepath( + root=root, node_name=node["name"]) + + attrs = node.get("attrs", {}) # allow 'attrs' to not exist + attrs["cacheFileName"] = cache_filename + node["attrs"] = attrs + + return fur_settings diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index b0a60b77f4..cf6db00e9a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -25,9 +25,6 @@ class ExtractYetiCache(openpype.api.Extractor): # Define extract output file path dirname = self.staging_dir(instance) - # Yeti related staging dirs - data_file = os.path.join(dirname, "yeti.fursettings") - # Collect information for writing cache start_frame = instance.data["frameStartHandle"] end_frame = instance.data["frameEndHandle"] @@ -57,32 +54,35 @@ class ExtractYetiCache(openpype.api.Extractor): cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] self.log.info("Writing metadata file") - settings = instance.data.get("fursettings", None) - if settings is not None: - with open(data_file, "w") as fp: - json.dump(settings, fp, ensure_ascii=False) + settings = instance.data["fursettings"] + fursettings_path = os.path.join(dirname, "yeti.fursettings") + with open(fursettings_path, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) # build representations if "representations" not in instance.data: instance.data["representations"] = [] self.log.info("cache files: {}".format(cache_files[0])) - instance.data["representations"].append( - { - 'name': 'fur', - 'ext': 'fur', - 'files': cache_files[0] if len(cache_files) == 1 else cache_files, - 'stagingDir': dirname, - 'frameStart': int(start_frame), - 'frameEnd': int(end_frame) - } - ) + + # Workaround: We do not explicitly register these files with the + # representation solely so that we can write multiple sequences + # a single Subset without renaming - it's a bit of a hack + # TODO: Implement better way to manage this sort of integration + if 'transfers' not in instance.data: + instance.data['transfers'] = [] + + publish_dir = instance.data["publishDir"] + for cache_filename in cache_files: + src = os.path.join(dirname, cache_filename) + dst = os.path.join(publish_dir, os.path.basename(cache_filename)) + instance.data['transfers'].append([src, dst]) instance.data["representations"].append( { - 'name': 'fursettings', + 'name': 'fur', 'ext': 'fursettings', - 'files': os.path.basename(data_file), + 'files': os.path.basename(fursettings_path), 'stagingDir': dirname } ) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index f981c4fe50..6e21bffa4e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -124,8 +124,8 @@ class ExtractYetiRig(openpype.api.Extractor): settings_path = os.path.join(dirname, "yeti.rigsettings") # Yeti related staging dirs - maya_path = os.path.join( - dirname, "yeti_rig.{}".format(self.scene_type)) + maya_path = os.path.join(dirname, + "yeti_rig.{}".format(self.scene_type)) self.log.info("Writing metadata file") From 87f7fa5470ed5fb74433633810bf379d92a3b58e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:32:02 +0200 Subject: [PATCH 026/258] Format name correctly for sequences on load --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 9752188551..8435ba2493 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -269,15 +269,8 @@ class YetiCacheLoader(load.LoaderPlugin): assert len(collections) == 1, "This is a bug" collection = collections[0] - # Assume padding from the first frame since clique returns 0 if the - # sequence contains no files padded with a zero at the start (e.g. - # a sequence starting at 1001) - padding = len(str(collection.indexes[0])) - - fname = "{head}%0{padding}d{tail}".format(collection.head, - padding, - collection.tail) - + # Formats name as {head}%d{tail} like cache.%04d.fur + fname = collection.format("{head}{padding}{tail}") return os.path.join(root, fname) def create_node(self, namespace, node_settings): From 5ee40df9c13b0eaddd94810b5177061d2a1dde87 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:25:30 +0200 Subject: [PATCH 027/258] Nuke: passing bake presets to instance data for downstream processing --- .../nuke/plugins/publish/extract_review_data_mov.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 2a79d600ba..384ed5f2ef 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import re import pyblish.api import openpype @@ -50,6 +51,7 @@ class ExtractReviewDataMov(openpype.api.Extractor): with maintained_selection(): generated_repres = [] for o_name, o_data in self.outputs.items(): + self.log.debug("o_name: {}, o_data: {}".format(o_name, pformat(o_data))) f_families = o_data["filter"]["families"] f_task_types = o_data["filter"]["task_types"] f_subsets = o_data["filter"]["subsets"] @@ -88,7 +90,13 @@ class ExtractReviewDataMov(openpype.api.Extractor): # check if settings have more then one preset # so we dont need to add outputName to representation # in case there is only one preset - multiple_presets = bool(len(self.outputs.keys()) > 1) + multiple_presets = len(self.outputs.keys()) > 1 + + # adding bake presets to instance data for other plugins + if not instance.data.get("bakePresets"): + instance.data["bakePresets"] = {} + # add preset to bakePresets + instance.data["bakePresets"][o_name] = o_data # create exporter instance exporter = plugin.ExporterReviewMov( From 3da82c845ff13a85884744988a529cc9ca4abb52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:26:05 +0200 Subject: [PATCH 028/258] Nuke: multi bake stream slate creation --- .../plugins/publish/extract_slate_frame.py | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index fb52fc18b4..ed936abb91 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import nuke import copy @@ -15,7 +16,7 @@ class ExtractSlateFrame(openpype.api.Extractor): """ - order = pyblish.api.ExtractorOrder - 0.001 + order = pyblish.api.ExtractorOrder + 0.011 label = "Extract Slate Frame" families = ["slate"] @@ -40,15 +41,33 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("instance.data[families]: {}".format( instance.data["families"])) - self.render_slate(instance) + if instance.data.get("bakePresets"): + for o_name, o_data in instance.data["bakePresets"].items(): + self.log.info("_ o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.render_slate(instance, o_name, **o_data) + else: + viewer_process_swithes = { + "bake_viewer_process": True, + "bake_viewer_input_process": True + } + self.render_slate(instance, None, **viewer_process_swithes) + + def render_slate(self, instance, output_name=None, **kwargs): + # solve output name if any is set + _output_name = output_name or "" + if _output_name: + _output_name = "_" + _output_name + + bake_viewer_process = kwargs["bake_viewer_process"] + bake_viewer_input_process_node = kwargs[ + "bake_viewer_input_process"] - def render_slate(self, instance): node_subset_name = instance.data.get("name", None) node = instance[0] # group node self.log.info("Creating staging dir...") if "representations" not in instance.data: - instance.data["representations"] = list() + instance.data["representations"] = [] staging_dir = os.path.normpath( os.path.dirname(instance.data['path'])) @@ -76,7 +95,7 @@ class ExtractSlateFrame(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") - collected_frames_len = int(len(collection.indexes)) + collected_frames_len = len(collection.indexes) # get first and last frame first_frame = min(collection.indexes) - 1 @@ -100,24 +119,34 @@ class ExtractSlateFrame(openpype.api.Extractor): previous_node = node - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) + # only create colorspace baking if toggled on + if bake_viewer_process: + if bake_viewer_input_process_node: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) - if not self.viewer_lut_raw: - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + if not self.viewer_lut_raw: + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") - file = fhead + "slate.png" + file = fhead[:-1] + _output_name + "_slate.png" path = os.path.join(staging_dir, file).replace("\\", "/") - instance.data["slateFrame"] = path + + # add slate path to `slateFrames` instance data attr + if not instance.data.get("slateFrames"): + instance.data["slateFrames"] = {} + + instance.data["slateFrames"][output_name or "*"] = path + + # create write node write_node["file"].setValue(path) write_node["file_type"].setValue("png") write_node["raw"].setValue(1) @@ -132,9 +161,6 @@ class ExtractSlateFrame(openpype.api.Extractor): # also render slate as sequence frame nuke.execute(node_subset_name, int(first_frame), int(last_frame)) - self.log.debug( - "slate frame path: {}".format(instance.data["slateFrame"])) - # Clean up for node in temporary_nodes: nuke.delete(node) @@ -221,5 +247,5 @@ class ExtractSlateFrame(openpype.api.Extractor): )) except NameError: self.log.warning(( - "Failed to set value \"{}\" on node attribute \"{}\"" + "Failed to set value \"{0}\" on node attribute \"{0}\"" ).format(value)) From 05b96bd0237c663adb14311ef83d4981aae7a14d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:26:41 +0200 Subject: [PATCH 029/258] Nuke: multi bake stream thumbnail creation --- .../nuke/plugins/publish/extract_thumbnail.py | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ef6d486ca2..03ae57f281 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -17,7 +17,7 @@ class ExtractThumbnail(openpype.api.Extractor): """ - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.011 label = "Extract Thumbnail" families = ["review"] @@ -35,9 +35,26 @@ class ExtractThumbnail(openpype.api.Extractor): self.log.debug("instance.data[families]: {}".format( instance.data["families"])) - self.render_thumbnail(instance) + if instance.data.get("bakePresets"): + for o_name, o_data in instance.data["bakePresets"].items(): + self.render_thumbnail(instance, o_name, **o_data) + else: + viewer_process_swithes = { + "bake_viewer_process": True, + "bake_viewer_input_process": True + } + self.render_thumbnail(instance, None, **viewer_process_swithes) + + def render_thumbnail(self, instance, output_name=None, **kwargs): + # solve output name if any is set + output_name = output_name or "" + if output_name: + output_name = "_" + output_name + + bake_viewer_process = kwargs["bake_viewer_process"] + bake_viewer_input_process_node = kwargs[ + "bake_viewer_input_process"] - def render_thumbnail(self, instance): node = instance[0] # group node self.log.info("Creating staging dir...") @@ -89,15 +106,7 @@ class ExtractThumbnail(openpype.api.Extractor): else: previous_node = node - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) - reformat_node = nuke.createNode("Reformat") - ref_node = self.nodes.get("Reformat", None) if ref_node: for k, v in ref_node: @@ -110,14 +119,24 @@ class ExtractThumbnail(openpype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # only create colorspace baking if toggled on + if bake_viewer_process: + if bake_viewer_input_process_node: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) + + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") - file = fhead + "jpg" + file = fhead[:-1] + output_name + ".jpg" name = "thumbnail" path = os.path.join(staging_dir, file).replace("\\", "/") instance.data["thumbnail"] = path From 6b90f0a2c1a66583d73d525f7c5d33eebc3ed197 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:27:19 +0200 Subject: [PATCH 030/258] Global: multi bake stream slate processing --- .../plugins/publish/extract_review_slate.py | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 49f0eac41d..af1d2b2983 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,4 +1,6 @@ import os +from pprint import pformat +import re import openpype.api import pyblish from openpype.lib import ( @@ -30,27 +32,11 @@ class ExtractReviewSlate(openpype.api.Extractor): raise RuntimeError("Burnin needs already created mov to work on.") suffix = "_slate" - slate_path = inst_data.get("slateFrame") + slates_data = inst_data["slateFrames"] + self.log.info("_ slates_data: {}".format(pformat(slates_data))) + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - slate_streams = get_ffprobe_streams(slate_path, self.log) - # Try to find first stream with defined 'width' and 'height' - # - this is to avoid order of streams where audio can be as first - # - there may be a better way (checking `codec_type`?)+ - slate_width = None - slate_height = None - for slate_stream in slate_streams: - if "width" in slate_stream and "height" in slate_stream: - slate_width = int(slate_stream["width"]) - slate_height = int(slate_stream["height"]) - break - - # Raise exception of any stream didn't define input resolution - if slate_width is None: - raise AssertionError(( - "FFprobe couldn't read resolution from input file: \"{}\"" - ).format(slate_path)) - if "reviewToWidth" in inst_data: use_legacy_code = True else: @@ -77,6 +63,12 @@ class ExtractReviewSlate(openpype.api.Extractor): input_path, self.log ) + # get slate data + slate_path = self._get_slate_path(input_file, slates_data) + self.log.info("_ slate_path: {}".format(slate_path)) + + slate_width, slate_height = self._get_slates_resolution(slate_path) + # Try to find first stream with defined 'width' and 'height' # - this is to avoid order of streams where audio can be as first # - there may be a better way (checking `codec_type`?) @@ -309,6 +301,44 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug(inst_data["representations"]) + def _get_slate_path(self, input_file, slates_data): + slate_path = None + for sl_n, _slate_path in slates_data.items(): + if "*" in sl_n: + slate_path = _slate_path + break + elif re.search(sl_n, input_file): + slate_path = _slate_path + break + + if not slate_path: + raise AttributeError( + "Missing slates paths: {}".format(slates_data)) + + return slate_path + + + def _get_slates_resolution(self, slate_path): + slate_streams = get_ffprobe_streams(slate_path, self.log) + # Try to find first stream with defined 'width' and 'height' + # - this is to avoid order of streams where audio can be as first + # - there may be a better way (checking `codec_type`?)+ + slate_width = None + slate_height = None + for slate_stream in slate_streams: + if "width" in slate_stream and "height" in slate_stream: + slate_width = int(slate_stream["width"]) + slate_height = int(slate_stream["height"]) + break + + # Raise exception of any stream didn't define input resolution + if slate_width is None: + raise AssertionError(( + "FFprobe couldn't read resolution from input file: \"{}\"" + ).format(slate_path)) + + return (slate_width, slate_height) + def add_video_filter_args(self, args, inserting_arg): """ Fixing video filter argumets to be one long string From 8453f9710f1fb305d917723eeee4cfea5c007062 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:27:52 +0300 Subject: [PATCH 031/258] Add profiles handling in schema aand for extractor --- openpype/plugins/publish/extract_jpeg_exr.py | 27 ++++++++++++- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +++++- .../schemas/schema_global_publish.json | 38 +++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 2d3ad1e8a8..c0363caf5b 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,10 +1,15 @@ import os import pyblish.api +from openpype.pipeline import ( + legacy_io, + KnownPublishError +) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -33,6 +38,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): + task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) + host_name = legacy_io.Session["AVALON_APP"] + family = instance.data["family"] + filtering_criteria = { + "hosts": host_name, + "families": family, + "tasks": task_name + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + if not profile: + return + + oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available + + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -121,7 +146,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index f0b2a7e555..5c5a14bf21 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index cedd0eed99..4ad56d8086 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,7 +40,17 @@ "-apply_trc gamma22" ], "output": [] - } + }, + "thumbnail_extraction_profiles": [ + { + "families": [ + "" + ], + "hosts": [], + "task_types": [], + "tasks": [] + } + ] }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index a3cbf0cfcd..4149c99348 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,6 +152,44 @@ "label": "FFmpeg output arguments" } ] + }, + { + "type": "list", + "key": "thumbnail_extraction_profiles", + "label": "Thumbnail Extraction profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + } + ] + } } ] }, From 0040f02ef71bd6156139c184170041d94f146a40 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:58:22 +0300 Subject: [PATCH 032/258] Style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c0363caf5b..0ad39aa720 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -53,7 +53,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_path = get_oiio_tools_path() # Raise an exception when oiiotool is not available - + if not os.path.exists(oiio_path): KnownPublishError( "OpenImageIO tool is not available on this machine." From 8e0b0156cad9fdf8d2c439bb4dc4078f50d468df Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:24:20 +0300 Subject: [PATCH 033/258] Fix comment typo --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index adb9bb2c3a..ee9a0f08de 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -533,7 +533,7 @@ def convert_input_paths_for_ffmpeg( output_dir, logger=None ): - """Contert source file to format supported in ffmpeg. + """Convert source file to format supported in ffmpeg. Currently can convert only exrs. The input filepaths should be files with same type. Information about input is loaded only from first found From 043ba41de7ba619be04b98e321deaa16487407d5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:59:53 +0300 Subject: [PATCH 034/258] Remove whitespace --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 0ad39aa720..070e9e1e08 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -144,7 +144,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) # run subprocess From 0b9bdbf1e61d586b9d29799cc01fbbb8f6c78ca6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 16:56:46 +0200 Subject: [PATCH 035/258] Nuke: abstracting functions used in plugins --- openpype/hosts/nuke/api/__init__.py | 9 ++++++- openpype/hosts/nuke/api/lib.py | 39 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index b571c4098c..77fe4503d3 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -26,7 +26,11 @@ from .pipeline import ( update_container, ) from .lib import ( - maintained_selection + maintained_selection, + reset_selection, + get_view_process_node, + duplicate_node + ) from .utils import ( @@ -58,6 +62,9 @@ __all__ = ( "update_container", "maintained_selection", + "reset_selection", + "get_view_process_node", + "duplicate_node", "colorspace_exists_on_node", "get_colorspace_list" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ba8aa7a8db..3871381451 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -707,6 +707,20 @@ def get_imageio_input_colorspace(filename): return preset_clrsp +def get_view_process_node(): + reset_selection() + + ipn_orig = None + for v in nuke.allNodes(filter="Viewer"): + ipn = v['input_process_node'].getValue() + if "VIEWER_INPUT" not in ipn: + ipn_orig = nuke.toNode(ipn) + ipn_orig.setSelected(True) + + if ipn_orig: + return duplicate_node(ipn_orig) + + def on_script_load(): ''' Callback for ffmpeg support ''' @@ -2549,6 +2563,31 @@ class DirmapCache: return cls._sync_module +def duplicate_node(node): + reset_selection() + + # select required node for duplication + node_orig = nuke.toNode(node.name()) + node_orig.setSelected(True) + + # copy selected to clipboard + nuke.nodeCopy("%clipboard%") + + # reset selection + reset_selection() + + # paste node and selection is on it only + nuke.nodePaste("%clipboard%") + + # assign to variable + dupli_node = nuke.selectedNode() + + # reset selection + reset_selection() + + return dupli_node + + def dirmap_file_name_filter(file_name): """Nuke callback function with single full path argument. From 6477257d4241446ce06bf09f7b840b4804c4bbfe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 16:59:33 +0200 Subject: [PATCH 036/258] Nuke: connecting to abstracted functions --- openpype/hosts/nuke/api/plugin.py | 41 +++---------------- .../nuke/plugins/publish/extract_thumbnail.py | 34 +++------------ 2 files changed, 10 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 2bad6f2c78..f2e17a4c89 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -14,11 +14,11 @@ from openpype.pipeline import ( from .lib import ( Knobby, check_subsetname_exists, - reset_selection, maintained_selection, set_avalon_knob_data, add_publish_knob, - get_nuke_imageio_settings + get_nuke_imageio_settings, + get_view_process_node ) @@ -215,37 +215,6 @@ class ExporterReview(object): self.data["representations"].append(repre) - def get_view_input_process_node(self): - """ - Will get any active view process. - - Arguments: - self (class): in object definition - - Returns: - nuke.Node: copy node of Input Process node - """ - reset_selection() - ipn_orig = None - for v in nuke.allNodes(filter="Viewer"): - ip = v["input_process"].getValue() - ipn = v["input_process_node"].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) - - if ipn_orig: - # copy selected to clipboard - nuke.nodeCopy("%clipboard%") - # reset selection - reset_selection() - # paste node and selection is on it only - nuke.nodePaste("%clipboard%") - # assign to variable - ipn = nuke.selectedNode() - - return ipn - def get_imageio_baking_profile(self): from . import lib as opnlib nuke_imageio = opnlib.get_nuke_imageio_settings() @@ -310,7 +279,7 @@ class ExporterReviewLut(ExporterReview): self._temp_nodes = [] self.log.info("Deleted nodes...") - def generate_lut(self): + def generate_lut(self, **kwargs): bake_viewer_process = kwargs["bake_viewer_process"] bake_viewer_input_process_node = kwargs[ "bake_viewer_input_process"] @@ -328,7 +297,7 @@ class ExporterReviewLut(ExporterReview): if bake_viewer_process: # Node View Process if bake_viewer_input_process_node: - ipn = self.get_view_input_process_node() + ipn = get_view_process_node() if ipn is not None: # connect ipn.setInput(0, self.previous_node) @@ -519,7 +488,7 @@ class ExporterReviewMov(ExporterReview): if bake_viewer_process: if bake_viewer_input_process_node: # View Process node - ipn = self.get_view_input_process_node() + ipn = get_view_process_node() if ipn is not None: # connect ipn.setInput(0, self.previous_node) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 03ae57f281..4b1c42cd12 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -3,7 +3,10 @@ import os import nuke import pyblish.api import openpype -from openpype.hosts.nuke.api.lib import maintained_selection +from openpype.hosts.nuke.api import ( + maintained_selection, + get_view_process_node +) if sys.version_info[0] >= 3: @@ -123,7 +126,7 @@ class ExtractThumbnail(openpype.api.Extractor): if bake_viewer_process: if bake_viewer_input_process_node: # get input process and connect it to baking - ipn = self.get_view_process_node() + ipn = get_view_process_node() if ipn is not None: ipn.setInput(0, previous_node) previous_node = ipn @@ -174,30 +177,3 @@ class ExtractThumbnail(openpype.api.Extractor): # Clean up for node in temporary_nodes: nuke.delete(node) - - def get_view_process_node(self): - - # Select only the target node - if nuke.selectedNodes(): - [n.setSelected(False) for n in nuke.selectedNodes()] - - ipn_orig = None - for v in [n for n in nuke.allNodes() - if "Viewer" == n.Class()]: - ip = v['input_process'].getValue() - ipn = v['input_process_node'].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) - - if ipn_orig: - nuke.nodeCopy('%clipboard%') - - # Deselect all - [n.setSelected(False) for n in nuke.selectedNodes()] - - nuke.nodePaste('%clipboard%') - - ipn = nuke.selectedNode() - - return ipn From 3b021e155e92abb8d1a0847e2dde9c409ba61d06 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:00:05 +0200 Subject: [PATCH 037/258] Nuke: refactory extract slate frame to bake viewer process only to thumbnail image --- .../plugins/publish/extract_slate_frame.py | 179 +++++++++--------- 1 file changed, 89 insertions(+), 90 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index ed936abb91..576f8b3440 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -6,7 +6,11 @@ import copy import pyblish.api import openpype -from openpype.hosts.nuke.api.lib import maintained_selection +from openpype.hosts.nuke.api import ( + maintained_selection, + duplicate_node, + get_view_process_node +) class ExtractSlateFrame(openpype.api.Extractor): @@ -30,11 +34,20 @@ class ExtractSlateFrame(openpype.api.Extractor): "f_vfx_scope_of_work": [False, ""] } + # constants + SLATE_TO_SEQUENCE_DONE = None + def process(self, instance): - if hasattr(self, "viewer_lut_raw"): - self.viewer_lut_raw = self.viewer_lut_raw - else: - self.viewer_lut_raw = False + self.fpath = instance.data["path"] + self.first_frame = instance.data["frameStartHandle"] + self.last_frame = instance.data["frameEndHandle"] + + self.log.info("Creating staging dir...") + + if "representations" not in instance.data: + instance.data["representations"] = [] + + self._create_staging_dir(instance) with maintained_selection(): self.log.debug("instance: {}".format(instance)) @@ -43,7 +56,8 @@ class ExtractSlateFrame(openpype.api.Extractor): if instance.data.get("bakePresets"): for o_name, o_data in instance.data["bakePresets"].items(): - self.log.info("_ o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.log.info("_ o_name: {}, o_data: {}".format( + o_name, pformat(o_data))) self.render_slate(instance, o_name, **o_data) else: viewer_process_swithes = { @@ -52,7 +66,21 @@ class ExtractSlateFrame(openpype.api.Extractor): } self.render_slate(instance, None, **viewer_process_swithes) + def _create_staging_dir(self, instance): + staging_dir = os.path.normpath( + os.path.dirname(self.fpath)) + + instance.data["stagingDir"] = staging_dir + + self.log.info( + "StagingDir `{0}`...".format(instance.data["stagingDir"])) + def render_slate(self, instance, output_name=None, **kwargs): + slate_node = instance.data["slateNode"] + + # fill slate node with comments + self.add_comment_slate_node(instance, slate_node) + # solve output name if any is set _output_name = output_name or "" if _output_name: @@ -62,31 +90,8 @@ class ExtractSlateFrame(openpype.api.Extractor): bake_viewer_input_process_node = kwargs[ "bake_viewer_input_process"] - node_subset_name = instance.data.get("name", None) - node = instance[0] # group node - self.log.info("Creating staging dir...") + slate_first_frame = self.first_frame - 1 - if "representations" not in instance.data: - instance.data["representations"] = [] - - staging_dir = os.path.normpath( - os.path.dirname(instance.data['path'])) - - instance.data["stagingDir"] = staging_dir - - self.log.info( - "StagingDir `{0}`...".format(instance.data["stagingDir"])) - - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - frame_length = int( - (frame_end - frame_start + 1) + (handle_start + handle_end) - ) - - temporary_nodes = [] collection = instance.data.get("collection", None) if collection: @@ -94,51 +99,61 @@ class ExtractSlateFrame(openpype.api.Extractor): fname = os.path.basename(collection.format( "{head}{padding}{tail}")) fhead = collection.format("{head}") - - collected_frames_len = len(collection.indexes) - - # get first and last frame - first_frame = min(collection.indexes) - 1 - self.log.info('frame_length: {}'.format(frame_length)) - self.log.info( - 'len(collection.indexes): {}'.format(collected_frames_len) - ) - if ("slate" in instance.data["families"]) \ - and (frame_length != collected_frames_len): - first_frame += 1 - - last_frame = first_frame else: - fname = os.path.basename(instance.data.get("path", None)) + fname = os.path.basename(self.fpath) fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStartHandle", None) - 1 - last_frame = first_frame if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - previous_node = node + self.log.debug("__ self.first_frame: {}".format(self.first_frame)) + self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) + + # Read node + r_node = nuke.createNode("Read") + r_node["file"].setValue(self.fpath) + r_node["first"].setValue(self.first_frame) + r_node["origfirst"].setValue(self.first_frame) + r_node["last"].setValue(self.last_frame) + r_node["origlast"].setValue(self.last_frame) + r_node["colorspace"].setValue(instance.data["colorspace"]) + previous_node = r_node + temporary_nodes = [previous_node] # only create colorspace baking if toggled on if bake_viewer_process: if bake_viewer_input_process_node: # get input process and connect it to baking - ipn = self.get_view_process_node() + ipn = get_view_process_node() if ipn is not None: ipn.setInput(0, previous_node) previous_node = ipn temporary_nodes.append(ipn) - if not self.viewer_lut_raw: - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # add duplicate slate node and connect to previous + duply_slate_node = duplicate_node(slate_node) + duply_slate_node.setInput(0, previous_node) + previous_node = duply_slate_node + temporary_nodes.append(duply_slate_node) + + # add viewer display transformation node + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) + + else: + # add duplicate slate node and connect to previous + duply_slate_node = duplicate_node(slate_node) + duply_slate_node.setInput(0, previous_node) + previous_node = duply_slate_node + temporary_nodes.append(duply_slate_node) # create write node write_node = nuke.createNode("Write") file = fhead[:-1] + _output_name + "_slate.png" - path = os.path.join(staging_dir, file).replace("\\", "/") + path = os.path.join( + instance.data["stagingDir"], file).replace("\\", "/") # add slate path to `slateFrames` instance data attr if not instance.data.get("slateFrames"): @@ -153,47 +168,31 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.setInput(0, previous_node) temporary_nodes.append(write_node) - # fill slate node with comments - self.add_comment_slate_node(instance) - # Render frames - nuke.execute(write_node.name(), int(first_frame), int(last_frame)) - # also render slate as sequence frame - nuke.execute(node_subset_name, int(first_frame), int(last_frame)) + nuke.execute( + write_node.name(), int(slate_first_frame), int(slate_first_frame)) - # Clean up - for node in temporary_nodes: - nuke.delete(node) + # also render image to sequence + self._render_slate_to_sequence(instance, slate_first_frame) - def get_view_process_node(self): - # Select only the target node - if nuke.selectedNodes(): - [n.setSelected(False) for n in nuke.selectedNodes()] + # # Clean up + # for node in temporary_nodes: + # nuke.delete(node) - ipn_orig = None - for v in [n for n in nuke.allNodes() - if "Viewer" in n.Class()]: - ip = v['input_process'].getValue() - ipn = v['input_process_node'].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) + def _render_slate_to_sequence(self, instance, slate_first_frame): + if not self.SLATE_TO_SEQUENCE_DONE: + node_subset_name = instance.data["name"] + # also render slate as sequence frame + nuke.execute( + node_subset_name, + int(slate_first_frame), + int(slate_first_frame) + ) - if ipn_orig: - nuke.nodeCopy('%clipboard%') + # mark as done + self.SLATE_TO_SEQUENCE_DONE = True - [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all - - nuke.nodePaste('%clipboard%') - - ipn = nuke.selectedNode() - - return ipn - - def add_comment_slate_node(self, instance): - node = instance.data.get("slateNode") - if not node: - return + def add_comment_slate_node(self, instance, node): comment = instance.context.data.get("comment") intent = instance.context.data.get("intent") From 4213f11e34ad4d917a0d48a3634f9b8a5159d1e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:00:56 +0200 Subject: [PATCH 038/258] Nuke: refactory slate frame concat to work backward compatible and with slate generator --- openpype/plugins/publish/extract_review_slate.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index af1d2b2983..01492476be 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -23,6 +23,8 @@ class ExtractReviewSlate(openpype.api.Extractor): families = ["slate", "review"] match = pyblish.api.Subset + SUFFIX = "_slate" + hosts = ["nuke", "shell"] optional = True @@ -31,8 +33,15 @@ class ExtractReviewSlate(openpype.api.Extractor): if "representations" not in inst_data: raise RuntimeError("Burnin needs already created mov to work on.") - suffix = "_slate" - slates_data = inst_data["slateFrames"] + # get slates frame from upstream + slates_data = inst_data.get("slateFrames") + if not slates_data: + # make it backward compatible and open for slates generator + # premium plugin + slates_data = { + "*": inst_data["slateFrame"] + } + self.log.info("_ slates_data: {}".format(pformat(slates_data))) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") @@ -125,7 +134,7 @@ class ExtractReviewSlate(openpype.api.Extractor): _remove_at_end = [] ext = os.path.splitext(input_file)[1] - output_file = input_file.replace(ext, "") + suffix + ext + output_file = input_file.replace(ext, "") + self.SUFFIX + ext _remove_at_end.append(input_path) From 7513b2b2f5d2fc6990e06a2c8d8351a06a403615 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:06:03 +0200 Subject: [PATCH 039/258] Deadline: making sure slate frames data are transfered to metadata.json --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..ed502f8fd2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -145,7 +145,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # mapping of instance properties to be transfered to new instance for every # specified family instance_transfer = { - "slate": ["slateFrame"], + "slate": ["slateFrames"], "review": ["lutPath"], "render2d": ["bakingNukeScripts", "version"], "renderlayer": ["convertToScanline"] From e258bfcc26a04347089941f881758009a885c803 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:17:29 +0200 Subject: [PATCH 040/258] hound --- openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py | 3 ++- openpype/plugins/publish/extract_review_slate.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 384ed5f2ef..5ea7c352b9 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -51,7 +51,8 @@ class ExtractReviewDataMov(openpype.api.Extractor): with maintained_selection(): generated_repres = [] for o_name, o_data in self.outputs.items(): - self.log.debug("o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.log.debug( + "o_name: {}, o_data: {}".format(o_name, pformat(o_data))) f_families = o_data["filter"]["families"] f_task_types = o_data["filter"]["task_types"] f_subsets = o_data["filter"]["subsets"] diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 01492476be..f2aed22c0e 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -326,7 +326,6 @@ class ExtractReviewSlate(openpype.api.Extractor): return slate_path - def _get_slates_resolution(self, slate_path): slate_streams = get_ffprobe_streams(slate_path, self.log) # Try to find first stream with defined 'width' and 'height' From c50cab558c80664e332d9831798d2754df1b89e1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:22:33 +0300 Subject: [PATCH 041/258] initial refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 191 +++++++++++-------- 1 file changed, 107 insertions(+), 84 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 070e9e1e08..52a678e9ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + is_oiio_supported, filter_profiles, run_subprocess, @@ -89,99 +90,121 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - - self.log.info("output {}".format(full_output_path)) - - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} - - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - - # If its a movie file, we just want one frame. + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": + do_convert = should_convert_for_ffmpeg(full_input_path) + # If result is None the requirement of conversion can't be + # determined + if do_convert is None: + self.log.info(( + "Can't determine if representation requires conversion." + " Skipped." + )) + continue + + # Do conversion if needed + # - change staging dir of source representation + # - must be set back after output definitions processing + convert_dir = None + if do_convert: + convert_dir = get_transcode_temp_directory() + filename = os.path.basename(full_input_path) + convert_input_paths_for_ffmpeg( + [full_input_path], + convert_dir, + self.log + ) + full_input_path = os.path.join(convert_dir, filename) + + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!!!" + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) + # Cleanup temp folder + if convert_dir is not None and os.path.exists(convert_dir): + shutil.rmtree(convert_dir) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created - break + elif repre["ext"] == "exr": + oiio_support = is_oiio_supported() + + if oiio_support: + oiio_tool_path = get_oiio_tools_path() + args = [oiio_tool_path] + + ext = os.path.splitext(input_path)[1][1:] + if ext in self.movie_extensions: + args.extend(["--subimage", str(int(input_frame))]) + else: + args.extend(["--frames", str(int(input_frame))]) + + if ext == "exr": + args.extend(["--powc", "0.45,0.45,0.45,1.0"]) + + args.extend([input_path, "-o", output_path]) + output = openpype.api.run_subprocess(args) + + failed_output = "oiiotool produced no output." + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args) + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which representation + # will be thumbnail created + break def _get_filtered_repres(self, instance): filtered_repres = [] From d992935468c488fdd4fe783cd7ce797d6f27ca50 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:31:56 +0300 Subject: [PATCH 042/258] name probesize and duration to max_int --- openpype/plugins/publish/extract_jpeg_exr.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52a678e9ef..066272fd01 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,4 +1,5 @@ import os +from urllib.parse import MAX_CACHE_SIZE import pyblish.api from openpype.pipeline import ( @@ -131,6 +132,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) # override file if already exists jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) # use same input args like with mov jpeg_items.extend(ffmpeg_args.get("input") or []) # input file From cc4efe7638340e0ab64857f6e00b676cfa4bb77e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 06:02:00 +0300 Subject: [PATCH 043/258] style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 066272fd01..52457ebb31 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -185,7 +185,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - + if oiio_support: oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] From 70c5366134ccafb066c40097907b958496392190 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 07:25:09 +0300 Subject: [PATCH 044/258] Additional oiiotool conversion refactoring, some style fixes --- openpype/plugins/publish/extract_jpeg_exr.py | 123 +++++++++++-------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52457ebb31..bb3b80b7ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -91,38 +91,41 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - # If it's a movie file, use ffmpeg + + + # Presumably the following is not needed since we are being + # explicit + # TODO: Test cases, cleanup. + # do_convert = should_convert_for_ffmpeg(full_input_path) + # # If result is None the requirement of conversion can't be + # # determined + # if do_convert is None: + # self.log.info(( + # "Can't determine if representation requires conversion." # noqa + # " Skipped." + # )) + # continue + # + # # Do conversion if needed + # # - change staging dir of source representation + # # - must be set back after output definitions processing + # convert_dir = None + # if do_convert: + # convert_dir = get_transcode_temp_directory() + # filename = os.path.basename(full_input_path) + # convert_input_paths_for_ffmpeg( + # [full_input_path], + # convert_dir, + # self.log + # ) + # full_input_path = os.path.join(convert_dir, filename) + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - self.log.info("output {}".format(full_output_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") @@ -178,37 +181,49 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) - elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - if oiio_support: + # TODO: Add resolution checking, possibly check for other + # places where things may break oiio_tool_path = get_oiio_tools_path() + full_output_path = os.path.join(stagingdir, jpeg_file) args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) - ext = os.path.splitext(input_path)[1][1:] - if ext in self.movie_extensions: - args.extend(["--subimage", str(int(input_frame))]) - else: - args.extend(["--frames", str(int(input_frame))]) - - if ext == "exr": - args.extend(["--powc", "0.45,0.45,0.45,1.0"]) - - args.extend([input_path, "-o", output_path]) - output = openpype.api.run_subprocess(args) - + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." + output = run_subprocess(args) if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created break def _get_filtered_repres(self, instance): From cd975beeec54fc6c13a808db8ae085c5cc9560b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 14:00:41 +0200 Subject: [PATCH 045/258] Update openpype/hosts/nuke/plugins/publish/extract_slate_frame.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/nuke/plugins/publish/extract_slate_frame.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 576f8b3440..2d20c1747c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -75,7 +75,13 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.info( "StagingDir `{0}`...".format(instance.data["stagingDir"])) - def render_slate(self, instance, output_name=None, **kwargs): + def render_slate( + self, + instance, + bake_viewer_process, + bake_viewer_input_process, + output_name=None + ): slate_node = instance.data["slateNode"] # fill slate node with comments From 20b7b5dca4c44cff55538bc7b9a13705edab7206 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 27 May 2022 12:39:39 +0300 Subject: [PATCH 046/258] Style fixes, remove unused imports --- openpype/plugins/publish/extract_jpeg_exr.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index bb3b80b7ef..771ffc0aed 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,8 +1,7 @@ import os -from urllib.parse import MAX_CACHE_SIZE import pyblish.api -from openpype.pipeline import ( +from openpype.pipeline import ( legacy_io, KnownPublishError ) @@ -14,14 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, - - get_transcode_temp_directory, - convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg ) -import shutil - class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" @@ -91,8 +84,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -149,7 +140,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) @@ -207,8 +197,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): output = run_subprocess(args) if failed_output in output: raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) - + "oiiotool processing failed. Args: {}".format(args)) # noqa new_repre = { "name": "thumbnail", "ext": "jpg", From c4115f61a7571ca157c41bbd6d4984073527b98d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:02:39 +0200 Subject: [PATCH 047/258] Nuke: removing kwargs with more explicit attribute inputs --- .../plugins/publish/extract_slate_frame.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 2d20c1747c..c9362329a6 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -58,13 +58,20 @@ class ExtractSlateFrame(openpype.api.Extractor): for o_name, o_data in instance.data["bakePresets"].items(): self.log.info("_ o_name: {}, o_data: {}".format( o_name, pformat(o_data))) - self.render_slate(instance, o_name, **o_data) + self.render_slate( + instance, + bake_viewer_process=o_data[ + "bake_viewer_process"], + bake_viewer_input_process=o_data[ + "bake_viewer_input_process"], + output_name=o_name + ) else: - viewer_process_swithes = { - "bake_viewer_process": True, - "bake_viewer_input_process": True - } - self.render_slate(instance, None, **viewer_process_swithes) + self.render_slate( + instance, + bake_viewer_process=True, + bake_viewer_input_process=True + ) def _create_staging_dir(self, instance): staging_dir = os.path.normpath( @@ -92,10 +99,6 @@ class ExtractSlateFrame(openpype.api.Extractor): if _output_name: _output_name = "_" + _output_name - bake_viewer_process = kwargs["bake_viewer_process"] - bake_viewer_input_process_node = kwargs[ - "bake_viewer_input_process"] - slate_first_frame = self.first_frame - 1 collection = instance.data.get("collection", None) @@ -128,7 +131,7 @@ class ExtractSlateFrame(openpype.api.Extractor): # only create colorspace baking if toggled on if bake_viewer_process: - if bake_viewer_input_process_node: + if bake_viewer_input_process: # get input process and connect it to baking ipn = get_view_process_node() if ipn is not None: From 709cf99ce0ebebafd936bd67f3c716e60c0081fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:08:52 +0200 Subject: [PATCH 048/258] Nuke: adding docstring to slate renderer function --- .../plugins/publish/extract_slate_frame.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index c9362329a6..01759e8b60 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -60,18 +60,12 @@ class ExtractSlateFrame(openpype.api.Extractor): o_name, pformat(o_data))) self.render_slate( instance, - bake_viewer_process=o_data[ - "bake_viewer_process"], - bake_viewer_input_process=o_data[ - "bake_viewer_input_process"], - output_name=o_name + o_name, + o_data["bake_viewer_process"], + o_data["bake_viewer_input_process"] ) else: - self.render_slate( - instance, - bake_viewer_process=True, - bake_viewer_input_process=True - ) + self.render_slate(instance) def _create_staging_dir(self, instance): staging_dir = os.path.normpath( @@ -85,10 +79,21 @@ class ExtractSlateFrame(openpype.api.Extractor): def render_slate( self, instance, - bake_viewer_process, - bake_viewer_input_process, - output_name=None + output_name=None, + bake_viewer_process=True, + bake_viewer_input_process=True ): + """Slate frame renderer + + Args: + instance (PyblishInstance): Pyblish instance with subset data + output_name (str, optional): + Slate variation name. Defaults to None. + bake_viewer_process (bool, optional): + Switch for viewer profile baking. Defaults to True. + bake_viewer_input_process (bool, optional): + Switch for input process node baking. Defaults to True. + """ slate_node = instance.data["slateNode"] # fill slate node with comments From d2bc5d46d530ece81e77ef8187d3bcbcc026eb15 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:36:02 +0200 Subject: [PATCH 049/258] Nuke: releasing plugin attributes and distributing them directly --- .../plugins/publish/extract_slate_frame.py | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 01759e8b60..87cb333ae1 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -27,22 +27,13 @@ class ExtractSlateFrame(openpype.api.Extractor): hosts = ["nuke"] # Settings values - # - can be extended by other attributes from node in the future key_value_mapping = { "f_submission_note": [True, "{comment}"], "f_submitting_for": [True, "{intent[value]}"], "f_vfx_scope_of_work": [False, ""] } - # constants - SLATE_TO_SEQUENCE_DONE = None - def process(self, instance): - self.fpath = instance.data["path"] - self.first_frame = instance.data["frameStartHandle"] - self.last_frame = instance.data["frameEndHandle"] - - self.log.info("Creating staging dir...") if "representations" not in instance.data: instance.data["representations"] = [] @@ -65,11 +56,18 @@ class ExtractSlateFrame(openpype.api.Extractor): o_data["bake_viewer_input_process"] ) else: + # backward compatibility self.render_slate(instance) + # also render image to sequence + self._render_slate_to_sequence(instance) + def _create_staging_dir(self, instance): + + self.log.info("Creating staging dir...") + staging_dir = os.path.normpath( - os.path.dirname(self.fpath)) + os.path.dirname(instance.data["path"])) instance.data["stagingDir"] = staging_dir @@ -96,6 +94,13 @@ class ExtractSlateFrame(openpype.api.Extractor): """ slate_node = instance.data["slateNode"] + # rendering path from group write node + fpath = instance.data["path"] + + # instance frame range with handles + first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] + # fill slate node with comments self.add_comment_slate_node(instance, slate_node) @@ -104,7 +109,7 @@ class ExtractSlateFrame(openpype.api.Extractor): if _output_name: _output_name = "_" + _output_name - slate_first_frame = self.first_frame - 1 + slate_first_frame = first_frame - 1 collection = instance.data.get("collection", None) @@ -114,22 +119,22 @@ class ExtractSlateFrame(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") else: - fname = os.path.basename(self.fpath) + fname = os.path.basename(fpath) fhead = os.path.splitext(fname)[0] + "." if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - self.log.debug("__ self.first_frame: {}".format(self.first_frame)) + self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) # Read node r_node = nuke.createNode("Read") - r_node["file"].setValue(self.fpath) - r_node["first"].setValue(self.first_frame) - r_node["origfirst"].setValue(self.first_frame) - r_node["last"].setValue(self.last_frame) - r_node["origlast"].setValue(self.last_frame) + r_node["file"].setValue(fpath) + r_node["first"].setValue(first_frame) + r_node["origfirst"].setValue(first_frame) + r_node["last"].setValue(last_frame) + r_node["origlast"].setValue(last_frame) r_node["colorspace"].setValue(instance.data["colorspace"]) previous_node = r_node temporary_nodes = [previous_node] @@ -186,25 +191,21 @@ class ExtractSlateFrame(openpype.api.Extractor): nuke.execute( write_node.name(), int(slate_first_frame), int(slate_first_frame)) - # also render image to sequence - self._render_slate_to_sequence(instance, slate_first_frame) + # Clean up + for node in temporary_nodes: + nuke.delete(node) - # # Clean up - # for node in temporary_nodes: - # nuke.delete(node) + def _render_slate_to_sequence(self, instance): + # set slate frame + first_frame = instance.data["frameStartHandle"] + slate_first_frame = first_frame - 1 - def _render_slate_to_sequence(self, instance, slate_first_frame): - if not self.SLATE_TO_SEQUENCE_DONE: - node_subset_name = instance.data["name"] - # also render slate as sequence frame - nuke.execute( - node_subset_name, - int(slate_first_frame), - int(slate_first_frame) - ) - - # mark as done - self.SLATE_TO_SEQUENCE_DONE = True + # render slate as sequence frame + nuke.execute( + instance.data["name"], + int(slate_first_frame), + int(slate_first_frame) + ) def add_comment_slate_node(self, instance, node): @@ -225,8 +226,8 @@ class ExtractSlateFrame(openpype.api.Extractor): "intent": intent }) - for key, value in self.key_value_mapping.items(): - enabled, template = value + for key, _values in self.key_value_mapping.items(): + enabled, template = _values if not enabled: self.log.debug("Key \"{}\" is disabled".format(key)) continue From 9afdf50bdc43ddbc014abd0ce4143d1755e79495 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 30 May 2022 15:18:51 +0300 Subject: [PATCH 050/258] initial conversion refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 153 +++++++++++++------ 1 file changed, 106 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 771ffc0aed..88fa627183 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -73,17 +73,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - repre_files = repre["files"] - if not isinstance(repre_files, (list, tuple)): - input_file = repre_files - else: - file_index = int(float(len(repre_files)) * 0.5) - input_file = repre_files[file_index] - - stagingdir = os.path.normpath(repre["stagingDir"]) - - full_input_path = os.path.join(stagingdir, input_file) - self.log.info("input {}".format(full_input_path)) # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -110,6 +99,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # self.log # ) # full_input_path = os.path.join(convert_dir, filename) + repre_files = repre["files"] + if not isinstance(repre_files, (list, tuple)): + input_file = repre_files + else: + file_index = int(float(len(repre_files)) * 0.5) + input_file = repre_files[file_index] + + stagingdir = os.path.normpath(repre["stagingDir"]) + + full_input_path = os.path.join(stagingdir, input_file) + self.log.info("input {}".format(full_input_path)) filename = os.path.splitext(input_file)[0] if not filename.endswith('.'): filename += "." @@ -178,42 +178,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # places where things may break oiio_tool_path = get_oiio_tools_path() full_output_path = os.path.join(stagingdir, jpeg_file) - args = [oiio_tool_path] - oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path - ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) - - # raise error if there is no ouptput - if not os.path.exists(full_input_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(full_input_path)) - raise AssertionError("OIIO tool conversion failed") - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created - break + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -233,3 +198,97 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres + + + def create_thumbnail_oiio(self, src_path, dst_path): + args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) + + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") + failed_output = "oiiotool produced no output." + output = run_subprocess(args) + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) # noqa + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created + + def create_thumbnail_ffmpeg(self, src_path, dst_path): + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) + + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) \ No newline at end of file From b6e6b7ae69f92aa540a3af146249d8ba0db4fd93 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 31 May 2022 15:17:52 +0300 Subject: [PATCH 051/258] refactor cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 88fa627183..8211c1a255 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -46,13 +46,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -167,18 +163,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "thumbnail": True, "tags": ["thumbnail"] } - # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - elif repre["ext"] == "exr": - oiio_support = is_oiio_supported() - if oiio_support: - # TODO: Add resolution checking, possibly check for other - # places where things may break - oiio_tool_path = get_oiio_tools_path() - full_output_path = os.path.join(stagingdir, jpeg_file) - + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -199,22 +187,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - - def create_thumbnail_oiio(self, src_path, dst_path): + def create_thumbnail_oiio(self, instance, src_path, dst_path): + oiio_path = get_oiio_tools_path() + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path + src_path, "-o", + dst_path ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) # raise error if there is no ouptput - if not os.path.exists(full_input_path): + if not os.path.exists(src_path): self.log.error( ("File {} was not converted " - "by oiio tool!").format(full_input_path)) + "by oiio tool!").format(src_path)) raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." output = run_subprocess(args) From 735942a6f53ca3a36ab1b60ebe462b25a5841abe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 May 2022 16:44:18 +0200 Subject: [PATCH 052/258] Nuke: simplify duplicate node function --- openpype/hosts/nuke/api/lib.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3871381451..7bb8c1e3d1 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2567,8 +2567,7 @@ def duplicate_node(node): reset_selection() # select required node for duplication - node_orig = nuke.toNode(node.name()) - node_orig.setSelected(True) + node.setSelected(True) # copy selected to clipboard nuke.nodeCopy("%clipboard%") @@ -2577,10 +2576,7 @@ def duplicate_node(node): reset_selection() # paste node and selection is on it only - nuke.nodePaste("%clipboard%") - - # assign to variable - dupli_node = nuke.selectedNode() + dupli_node = nuke.nodePaste("%clipboard%") # reset selection reset_selection() From 2de4044167d5940c13bd768ad29879aa9d007b47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 May 2022 16:44:37 +0200 Subject: [PATCH 053/258] Nuke: remove callback always --- openpype/hosts/nuke/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7bb8c1e3d1..3062091ba0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2384,6 +2384,8 @@ def process_workfile_builder(): env_value_to_bool, get_custom_workfile_template ) + # to avoid looping of the callback, remove it! + nuke.removeOnCreate(process_workfile_builder, nodeClass="Root") # get state from settings workfile_builder = get_current_project_settings()["nuke"].get( @@ -2439,9 +2441,6 @@ def process_workfile_builder(): if not openlv_on or not os.path.exists(last_workfile_path): return - # to avoid looping of the callback, remove it! - nuke.removeOnCreate(process_workfile_builder, nodeClass="Root") - log.info("Opening last workfile...") # open workfile open_file(last_workfile_path) From ca9c06df18bd7ff16ee7bae5ba7f4ef51ac383f7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:03:05 +0300 Subject: [PATCH 054/258] continue refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 104 +++++++------------ 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8211c1a255..4ffacf2600 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -13,6 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, + + execute, ) @@ -46,9 +48,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - - # Raise an exception when oiiotool is not available - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -67,7 +66,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres = self._get_filtered_repres(instance) - for repre in filtered_repres: # Presumably the following is not needed since we are being # explicit @@ -112,61 +110,42 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - if repre["ext"] == "mov": - self.log.info("output {}".format(full_output_path)) - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool + # isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + else: + # Check if the file can be read by OIIO + args = [ + oiiotool_path, "--info", "-i", src_path + ] + returncode = execute(args, silent=True) + # If the input can read by OIIO then use OIIO method for conversion otherwise use ffmpeg + if returncode == 0: + + thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + else: + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # flag for large file sizes - max_int = 2147483647 - jpeg_items.append("-analyzeduration {}".format(max_int)) - jpeg_items.append("-probesize {}".format(max_int)) - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - # we just want one frame from movie files - jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] @@ -188,11 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, instance, src_path, dst_path): - oiio_path = get_oiio_tools_path() - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) + oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -202,12 +177,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise error if there is no ouptput - if not os.path.exists(src_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(src_path)) - raise AssertionError("OIIO tool conversion failed") + # raise an error if there's no output failed_output = "oiiotool produced no output." output = run_subprocess(args) if failed_output in output: @@ -230,7 +200,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(full_output_path)) + self.log.info("output {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From ebfef2a1b869c07eee950952bc38b97717281854 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:11:26 +0300 Subject: [PATCH 055/258] Cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++-------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4ffacf2600..15f90262dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -115,19 +115,20 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool # isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: # Check if the file can be read by OIIO args = [ - oiiotool_path, "--info", "-i", src_path + oiio_tool_path, "--info", "-i", full_output_path ] returncode = execute(args, silent=True) - # If the input can read by OIIO then use OIIO method for conversion otherwise use ffmpeg + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg if returncode == 0: - thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: @@ -166,7 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, instance, src_path, dst_path): + def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, @@ -183,21 +184,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if failed_output in output: raise ValueError( "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -217,14 +203,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("input") or []) # input file jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) + path_to_subprocess_arg(src_path) )) # output arguments from presets jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) + jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) # run subprocess From b720d46489cc88a2b08cb11964fad2b4d91af8e5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:31:20 +0300 Subject: [PATCH 056/258] continue refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 116 +++++-------------- 1 file changed, 29 insertions(+), 87 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 15f90262dc..8dc06bfe79 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -67,32 +67,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - # Presumably the following is not needed since we are being - # explicit - # TODO: Test cases, cleanup. - # do_convert = should_convert_for_ffmpeg(full_input_path) - # # If result is None the requirement of conversion can't be - # # determined - # if do_convert is None: - # self.log.info(( - # "Can't determine if representation requires conversion." # noqa - # " Skipped." - # )) - # continue - # - # # Do conversion if needed - # # - change staging dir of source representation - # # - must be set back after output definitions processing - # convert_dir = None - # if do_convert: - # convert_dir = get_transcode_temp_directory() - # filename = os.path.basename(full_input_path) - # convert_input_paths_for_ffmpeg( - # [full_input_path], - # convert_dir, - # self.log - # ) - # full_input_path = os.path.join(convert_dir, filename) repre_files = repre["files"] if not isinstance(repre_files, (list, tuple)): input_file = repre_files @@ -111,38 +85,38 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - thumbnail_created = False - # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool - # isn't available) - if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) - else: - # Check if the file can be read by OIIO - args = [ - oiio_tool_path, "--info", "-i", full_output_path - ] - returncode = execute(args, silent=True) - # If the input can read by OIIO then use OIIO method for - # conversion otherwise use ffmpeg - if returncode == 0: - - thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when + # oiiotool isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa + # Check if the file can be read by OIIO + args = [ + oiio_tool_path, "--info", "-i", full_output_path + ] + returncode = execute(args, silent=True) + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg + if returncode == 0: + + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + else: + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa - # Skip the rest of the process if the thumbnail wasn't created - if not thumbnail_created: + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - return + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } # adding representation self.log.debug("Adding: {}".format(new_repre)) @@ -169,7 +143,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() - args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", dst_path @@ -178,13 +151,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise an error if there's no output - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -213,30 +179,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( + run_subprocess( subprocess_command, shell=True, logger=self.log ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise - - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) \ No newline at end of file From 84c073dd3c45515a055d36a8eee29bfe3cd749cd Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:19:45 +0300 Subject: [PATCH 057/258] Further clean up and refactor. --- openpype/plugins/publish/extract_jpeg_exr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8dc06bfe79..a5a62c04ee 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,7 +3,6 @@ import os import pyblish.api from openpype.pipeline import ( legacy_io, - KnownPublishError ) from openpype.lib import ( get_ffmpeg_tool_path, @@ -83,15 +82,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filename += "." jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) - # If it's a movie file, use ffmpeg thumbnail_created = False # Try to use FFMPEG if OIIO is not supported (for cases when # oiiotool isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa else: # Check if the file can be read by OIIO + oiio_tool_path = get_oiio_tools_path() args = [ oiio_tool_path, "--info", "-i", full_output_path ] @@ -99,9 +98,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg if returncode == 0: - + self.log.info("Input can be read by OIIO, converting with oiiotool now.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: + self.log.info("converting with") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created @@ -142,6 +142,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -152,7 +153,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): run_subprocess(subprocess_exr, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(dst_path)) + self.log.info("outputting {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From 3b26f78335057810cd8cd9d23f1de3546524211a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:23:52 +0300 Subject: [PATCH 058/258] Revert "Add profiles handling in schema aand for extractor" This reverts commit 8453f9710f1fb305d917723eeee4cfea5c007062. --- openpype/plugins/publish/extract_jpeg_exr.py | 17 --------- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +----- .../schemas/schema_global_publish.json | 38 ------------------- repos/avalon-core | 1 + 5 files changed, 3 insertions(+), 67 deletions(-) create mode 160000 repos/avalon-core diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index a5a62c04ee..4a1fea2043 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,15 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - legacy_io, -) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, is_oiio_supported, - filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -34,19 +30,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): - task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) - host_name = legacy_io.Session["AVALON_APP"] - family = instance.data["family"] - filtering_criteria = { - "hosts": host_name, - "families": family, - "tasks": task_name - } - profile = filter_profiles(self.profiles, filtering_criteria, - logger=self.log) - if not profile: - return - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..f0b2a7e555 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 4ad56d8086..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,17 +40,7 @@ "-apply_trc gamma22" ], "output": [] - }, - "thumbnail_extraction_profiles": [ - { - "families": [ - "" - ], - "hosts": [], - "task_types": [], - "tasks": [] - } - ] + } }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 4149c99348..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,44 +152,6 @@ "label": "FFmpeg output arguments" } ] - }, - { - "type": "list", - "key": "thumbnail_extraction_profiles", - "label": "Thumbnail Extraction profiles", - "use_label_wrap": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "label", - "label": "" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - } - ] - } } ] }, diff --git a/repos/avalon-core b/repos/avalon-core new file mode 160000 index 0000000000..2fa14cea6f --- /dev/null +++ b/repos/avalon-core @@ -0,0 +1 @@ +Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From c4468664970bd93bbd074d7424042d4adee20c8e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:25:56 +0300 Subject: [PATCH 059/258] remove unused variable --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4a1fea2043..daf7430a32 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -27,7 +27,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None - oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) From 85748589ef4cdbda6ad65c59c3130ad2901dc0b4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Jun 2022 11:18:02 +0200 Subject: [PATCH 060/258] :bug: skip empty attributes --- openpype/hosts/maya/plugins/publish/collect_look.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 323bede761..9b6d1d999c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,6 +601,9 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) + if not source: + self.log.info("source is empty, skipping...") + continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the # paths as the computed patterns From 73b492c1fff78f4ece0e63dfc9bb0c83189ebe47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Jun 2022 15:53:40 +0200 Subject: [PATCH 061/258] Flame: adding `OPENPYPE_TEMP_DIR` to extractor --- .../publish/extract_subset_resources.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0bad3f7cfc..f3239af23e 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -1,5 +1,6 @@ import os import re +import tempfile from pprint import pformat from copy import deepcopy @@ -420,3 +421,28 @@ class ExtractSubsetResources(openpype.api.Extractor): "Path `{}` is containing more that one clip".format(path) ) return clips[0] + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + staging_dir = instance.data.get('stagingDir', None) + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") + + if not staging_dir: + if openpype_temp_dir and os.path.exists(openpype_temp_dir): + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=openpype_temp_dir + ) + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + return staging_dir From 2339e41b4c32afcbd4eb5d1a6d8ea2cadd58f057 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:19:22 +0200 Subject: [PATCH 062/258] Fix - removed unnecessary initialization --- openpype/modules/sync_server/sync_server_module.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5a1d8467ec..bd75172cc6 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1026,9 +1026,6 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ self.server_init() - from .tray.app import SyncServerWindow - self.widget = SyncServerWindow(self) - def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts From 688400ad7b7065f6db5322dd480bfe352cc2f62d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:20:33 +0200 Subject: [PATCH 063/258] Fix - removed unnecessary first query setSortable forces refresh itself --- openpype/modules/sync_server/tray/models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 7241cc3472..d3d36cdfd4 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -523,10 +523,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.query = self.get_query() self.default_query = list(self.get_query()) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) @@ -1003,9 +999,6 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.sort_criteria = self.DEFAULT_SORT self.query = self.get_query() - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) From 81a51696804915888daeeb42cf0b1d5f4c92e538 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 11:08:52 +0200 Subject: [PATCH 064/258] Fix - faster loop logic --- openpype/modules/sync_server/sync_server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 22eed01ef3..808d703616 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -282,10 +282,9 @@ class SyncServerThread(threading.Thread): import time start_time = None self.module.set_sync_project_settings() # clean cache - for collection, preset in self.module.sync_project_settings.\ - items(): - if collection not in self.module.get_enabled_projects(): - continue + enabled_projects = self.module.get_enabled_projects() + for collection in enabled_projects: + preset = self.module.sync_project_settings["collection"] start_time = time.time() local_site, remote_site = self._working_sites(collection) From 3c285d859a454107278470be43d00c205a0233e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:34:21 +0200 Subject: [PATCH 065/258] Fix - fixes Do creation of settings only in Thread as it is expensive operation. --- openpype/modules/sync_server/sync_server.py | 6 +++--- openpype/modules/sync_server/sync_server_module.py | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 808d703616..356a75f99d 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -280,13 +280,13 @@ class SyncServerThread(threading.Thread): while self.is_running and not self.module.is_paused(): try: import time - start_time = None + start_time = time.time() self.module.set_sync_project_settings() # clean cache + collection = None enabled_projects = self.module.get_enabled_projects() for collection in enabled_projects: - preset = self.module.sync_project_settings["collection"] + preset = self.module.sync_project_settings[collection] - start_time = time.time() local_site, remote_site = self._working_sites(collection) if not all([local_site, remote_site]): continue diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bd75172cc6..7f541d52e3 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1029,15 +1029,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts - from .sync_server import SyncServerThread - if not self.enabled: return - enabled_projects = self.get_enabled_projects() - if not enabled_projects: - self.enabled = False - return + from .sync_server import SyncServerThread self.lock = threading.Lock() @@ -1057,7 +1052,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.server_start() def server_start(self): - if self.sync_project_settings and self.enabled: + if self.enabled: self.sync_server_thread.start() else: log.info("No presets or active providers. " + @@ -1848,6 +1843,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Returns: (int): in seconds """ + if not project_name: + return 60 + ld = self.sync_project_settings[project_name]["config"]["loop_delay"] return int(ld) From 96115fac6ed2addf4908e0aacaf9d3d2dc77ad12 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:36:18 +0200 Subject: [PATCH 066/258] Fix - remove reset of Settings Let only thread do it, expensive operation potentially. --- openpype/modules/sync_server/tray/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index d3d36cdfd4..6b309312a2 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -378,7 +378,6 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): project (str): name of project """ self._project = project - self.sync_server.set_sync_project_settings() # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): return From d86c71c15bc00d31c5459b3d75a28fcd70813696 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 13:23:51 +0200 Subject: [PATCH 067/258] Fix - added disabled icon to Site Queue --- .../sync_server/resources/disabled.png | Bin 0 -> 2368 bytes .../modules/sync_server/sync_server_module.py | 17 +++++++-- openpype/modules/sync_server/tray/widgets.py | 34 ++++++++++++------ openpype/tools/loader/model.py | 2 +- openpype/tools/loader/widgets.py | 14 ++++---- 5 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 openpype/modules/sync_server/resources/disabled.png diff --git a/openpype/modules/sync_server/resources/disabled.png b/openpype/modules/sync_server/resources/disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e036d7ef6a12a353551ddfc3e8a4e099c6506dd7 GIT binary patch literal 2368 zcmb_e2~ZPP7>*VZ@L-&JptjTPQmu-cJ+dU(EfJBRpoYW5gAP_+c3;R!k`38Kf-}}? zwXI5VMkdyC#wymMmU>MOoz_|qi=#}bPFwG)^{8UedNAtX^d*EyJ6_XiW;Xlwz3+Yh z_y6yEZ+B5{_PAbggW@zAO|Ps>^CWb~f>%rw`aiDhoQrO;-pqVOqv>52yt)LEdJooU zqW4I)Y3ek~L|%~HS|G}HsP((O2(8hir24%;D26I#heeXdgtzS7hhvgx!lx!%D2q28 zI;G6nK4_htZ4+h}3!I3jj>1y>JR)#I6<~h1%cJmq6CUE_Q9Ia8;#dfx7Mt+2U_fk| zB^OJVeGoHfX+oeV3Nvt8gB>^o2Q?C7D266!illV}Me!`nGe#`@!%;Y&=-?-r$A*KU zCll^eRWDDHWo2dBGM!fT6_GT@aU{i%3_~CUp_F@6;3qsvVh4j6DuPe)s*>!%f{ef} zm#QWlNo~);?TyfSlyIF;#YjK!lC+iz<`e>oLIme6^|?a9MS+Aa=!PCuL0CG1^*Uu$ zR-Ez^s3XJw2!Lv9u|#4#R*Tym38APNvrro0f;<+j*vh?-oCFoQ)F;4Vms*6^VM4mJ1 zj4Z7;5PE}7AoLEFC4imd2)&rhGLY37jSzOsH_Jk4aP@=p+joK}3y9;9GCg2)z;1L9 zMnOjtw1IUH00NGHc88Is05H-t*TI(Klh9@auFkAMRU%?E>h(HEB|8YwkjxMQU?BmF zMuDJcgWdpjlwPpwf{qWFh|iQ1w7livJ!FL?;g(CnLcZaF5Zn(YTnHKgig>tNdQ3f@ zD{^>#nG+(?rx>I?x*|K>?)0MW}YqG$8pT@d}vrbm?~5ZlLWtKmaz`NH81=2)md} z6Oa-OqR3KugC3c-T~lZ2oeWDOZ?_xvzeo>r3r^rEg6MQ5@&BI8^ZD|r$voP9|6ej> z@JwxY82NAWg~mFazi1&tUFc8`HY2YS^bmO=K@X~v54~=fpA$+nnyw?W%xSjFgQo+j z#eMVo9yr_BJaq3H#T88puo3~=xOSm)L;sV~h?_|<%bVg0x0!8G`w!({>(>Q-YgxB! z%J%I$UL4#lf8x%DXhHvr-BEVwdh=@wr!=0UYf~D(ZvFPbWlovfz4eut>i7f0h9vAT z%&&?5NS#2I?>9}a9upWexqfqQ=Yx5RN8LXLcoToso*=KRZtk&Q z&-%~urkI+qbbn6@46Hwo+3)9lQ#GJ;-sqg^1M3H0+R{6U-4gFVPS%~lzS4hr!Rj@d zG9~8vPf5#f^h~jKyS(D#Yi(zaFa2|0emyt6KIg=|yGJ)BXH;HvT{=GCudQiAk4gvc ztP#3@;D4*2bj{?v`*)WGKCjzt)fzKOJj$!Lf4$iH@b&rhb?>P{^Szl%i7(!deS7aM zzNT(;%PvkkVpGA2;=^|)aOd+1AhR#AZTa1q=XN!UtG86&n!S1Qi4(cwUglj+>*}kw z_eWoQv&fQq?|bERwfrnM z>C9Ms=!$h|YexhgCj3!b@GvemCN?%ErmBVMcW6QF3v+glZdz;el5Owqy%~SyllUg~ T){1$-Z=9@*Y;(<+!ufv#nIl6P literal 0 HcmV?d00001 diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7f541d52e3..698b296a52 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,9 +926,22 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return enabled_projects - def is_project_enabled(self, project_name): + def is_project_enabled(self, project_name, single=False): + """Checks if 'project_name' is enabled for syncing. + 'get_sync_project_setting' is potentially expensive operation (pulls + settings for all projects if cached version is not available), using + project_settings for specific project should be faster. + Args: + project_name (str) + single (bool): use 'get_project_settings' method + """ if self.enabled: - project_settings = self.get_sync_project_setting(project_name) + if single: + project_settings = get_project_settings(project_name) + project_settings = \ + self._parse_sync_settings_from_settings(project_settings) + else: + project_settings = self.get_sync_project_setting(project_name) if project_settings and project_settings.get("enabled"): return True return False diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 6aae9562cf..18de4d311d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -122,11 +122,13 @@ class SyncProjectListWidget(QtWidgets.QWidget): self._model_reset = False selected_item = None - for project_name in self.sync_server.sync_project_settings.\ - keys(): + sync_settings = self.sync_server.sync_project_settings + for project_name in sync_settings.keys(): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") + elif not sync_settings["enabled"]: + icon = self._get_icon("disabled") else: icon = self._get_icon("synced") @@ -578,10 +580,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): + import time + log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server - self._selected_ids = set() # keep last selected _id txt_filter = QtWidgets.QLineEdit() @@ -600,8 +603,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] + start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) + log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -625,7 +631,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - + log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) + start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -633,27 +640,35 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - + log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) + start_time = time.time() horizontal_header = HorizontalHeader(self) - + log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setHorizontalHeader(horizontal_header) + log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setSortingEnabled(True) - + log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) + start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - + log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - + log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) + start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) + log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): @@ -963,7 +978,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent) self._parent = parent self.checked_values = {} - self.setModel(self._parent.model) self.setSectionsClickable(True) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e6bef0a33a 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -89,7 +89,7 @@ class BaseRepresentationModel(object): self._last_manager_cache = now_time sync_server = self._modules_manager.modules_by_name["sync_server"] - if sync_server.is_project_enabled(project_name): + if sync_server.is_project_enabled(project_name, single=True): active_site = sync_server.get_active_site(project_name) active_provider = sync_server.get_provider_for_site( project_name, active_site) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..5764085b6a 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -356,9 +356,10 @@ class SubsetWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) lib.change_visibility(self.model, self.view, "repre_info", enabled) @@ -1228,9 +1229,10 @@ class RepresentationWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) self.sync_server_enabled = enabled lib.change_visibility(self.model, self.tree_view, From 60bef01f6cd9a4f1233fcebf655254d52a8e3f04 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 14:18:58 +0200 Subject: [PATCH 068/258] Fix - removed unwanted logs --- openpype/modules/sync_server/tray/widgets.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 18de4d311d..049a3f0127 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -580,8 +580,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): - import time - log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server @@ -603,11 +601,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] - start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) - log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -631,8 +626,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) - start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -640,35 +633,22 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) - start_time = time.time() horizontal_header = HorizontalHeader(self) - log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setHorizontalHeader(horizontal_header) - log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setSortingEnabled(True) - log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) - start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) - start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): From 5526435e41554a685864218fd601dd276cfc0cdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:07:49 +0200 Subject: [PATCH 069/258] initial commit of client entities functions --- openpype/client/__init__.py | 59 +++ openpype/client/entities.py | 897 ++++++++++++++++++++++++++++++++++++ 2 files changed, 956 insertions(+) create mode 100644 openpype/client/__init__.py create mode 100644 openpype/client/entities.py diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py new file mode 100644 index 0000000000..861f828e67 --- /dev/null +++ b/openpype/client/__init__.py @@ -0,0 +1,59 @@ +from .entities import ( + get_projects, + get_project, + + get_asset, + get_assets, + get_asset_ids_with_subsets, + + get_subset, + get_subsets, + get_subset_families, + + get_version_by_name, + get_version, + get_versions, + get_last_versions, + get_last_version_for_subset, + get_hero_version, + get_hero_versions, + get_version_links, + + get_representation, + get_representation_by_name, + get_representations, + get_representations_parents, + + get_thumbnail, + get_thumbnail_id_from_source, +) + +__all__ = ( + "get_projects", + "get_project", + + "get_asset", + "get_assets", + "get_asset_ids_with_subsets", + + "get_subset", + "get_subsets", + "get_subset_families", + + "get_version_by_name", + "get_version", + "get_versions", + "get_last_versions", + "get_last_version_for_subset", + "get_hero_version", + "get_hero_versions", + "get_version_links", + + "get_representation", + "get_representation_by_name", + "get_representations", + "get_representations_parents", + + "get_thubmnail", + "get_thumbnail_id_from_source", +) diff --git a/openpype/client/entities.py b/openpype/client/entities.py new file mode 100644 index 0000000000..4b52f8cf2d --- /dev/null +++ b/openpype/client/entities.py @@ -0,0 +1,897 @@ +"""Unclear if these will have public functions like these. + +Goal is that most of functions here are called on (or with) an object +that has project name as a context (e.g. on 'ProjectEntity'?). + ++ We will need more specific functions doing wery specific queires really fast. +""" + +import os +import collections + +import six +from bson.objectid import ObjectId + +from openpype.lib.mongo import OpenPypeMongoConnection + + +def _get_project_connection(project_name=None): + db_name = os.environ.get("AVALON_DB") or "avalon" + mongodb = OpenPypeMongoConnection.get_mongo_client()[db_name] + if project_name: + return mongodb[project_name] + return mongodb + + +def _prepare_fields(fields): + if not fields: + return None + + output = { + field: True + for field in fields + } + if "_id" not in output: + output["_id"] = True + return output + + +def _convert_id(in_id): + if isinstance(in_id, six.string_types): + return ObjectId(in_id) + return in_id + + +def _convert_ids(in_ids): + _output = set() + for in_id in in_ids: + if in_id is not None: + _output.add(_convert_id(in_id)) + return list(_output) + + +def get_projects(active=True, inactive=False, fields=None): + mongodb = _get_project_connection() + for project_name in mongodb.collection_names(): + if project_name in ("system.indexes",): + continue + project_doc = get_project( + project_name, active=active, inactive=inactive, fields=fields + ) + if project_doc is not None: + yield project_doc + + +def get_project(project_name, active=True, inactive=False, fields=None): + # Skip if both are disabled + if not active and not inactive: + return None + + query_filter = {"type": "project"} + # Keep query untouched if both should be available + if active and inactive: + pass + + # Add filter to keep only active + elif active: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": True}, + ] + + # Add filter to keep only inactive + elif inactive: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": False}, + ] + + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_asset(project_name, asset_name=None, asset_id=None, fields=None): + query_filter = {"type": "asset"} + has_filter = False + if asset_name: + has_filter = True + query_filter["name"] = asset_name + + if asset_id: + has_filter = True + query_filter["_id"] = _convert_id(asset_id) + + # Avoid random asset quqery + if not has_filter: + return None + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_assets( + project_name, asset_ids=None, asset_names=None, archived=False, fields=None +): + asset_types = ["asset"] + if archived: + asset_types.append("archived_asset") + + if len(asset_types) == 1: + query_filter = {"type": asset_types[0]} + else: + query_filter = {"type": {"$in": asset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["_id"] = {"$in": asset_ids} + + if asset_names is not None: + if not asset_names: + return [] + query_filter["name"] = {"$in": list(asset_names)} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_asset_ids_with_subsets(project_name, asset_ids=None): + subset_query = { + "type": "subset" + } + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + subset_query["parent"] = {"$in": asset_ids} + + conn = _get_project_connection(project_name) + result = conn.aggregate([ + { + "$match": subset_query + }, + { + "$group": { + "_id": "$parent", + "count": {"$sum": 1} + } + } + ]) + asset_ids_with_subsets = [] + for item in result: + asset_id = item["_id"] + count = item["count"] + if count > 0: + asset_ids_with_subsets.append(asset_id) + return asset_ids_with_subsets + + +def get_subset( + project_name, + subset_id=None, + subset_name=None, + asset_id=None, + fields=None +): + """Single subset document by subset id or name and parent id. + + When subset id is defined it is not needed to add any other arguments but + subset name filter must be always combined with asset id (or subset id). + + Question: + This could be split into more functions? + + Args: + project_name (str): Name of project where to look for subset. + subset_id (ObjectId): Id of subset which should be found. + subset_name (str): Name of asset. Must be combined with 'asset_id' or + 'subset_id' arguments otherwise result is 'None'. + asset_id (ObjectId): Id of parent asset. Must be combined with + 'subset_name' or 'subset_id'. + fields (list): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + query_filters = {"type": "subset"} + has_valid_filters = False + if subset_id is not None: + query_filters["_id"] = _convert_id(asset_id) + has_valid_filters = True + + if subset_name is not None: + if asset_id is not None: + has_valid_filters = True + query_filters["name"] = subset_name + + if asset_id is not None: + query_filters["parent"] = _convert_id(asset_id) + + if not has_valid_filters: + return None + + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subsets( + project_name, + asset_ids=None, + subset_ids=None, + subset_names=None, + archived=False, + fields=None +): + subset_types = ["subset"] + if archived: + subset_types.append("archived_subset") + + if len(subset_types) == 1: + query_filter = {"type": subset_types[0]} + else: + query_filter = {"type": {"$in": subset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["parent"] = {"$in": asset_ids} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["_id"] = {"$in": subset_ids} + + if subset_names is not None: + if not subset_names: + return [] + query_filter["name"] = {"$in": list(subset_names)} + + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_subset_families(project_name, subset_ids=None): + subset_filter = { + "type": "subset" + } + if subset_ids is not None: + if not subset_ids: + return set() + subset_filter["_id"] = {"$in": list(subset_ids)} + + conn = _get_project_connection(project_name) + result = list(conn.aggregate([ + {"$match": subset_filter}, + {"$project": { + "family": {"$arrayElemAt": ["$data.families", 0]} + }}, + {"$group": { + "_id": "family_group", + "families": {"$addToSet": "$family"} + }} + ])) + if result: + return set(result[0]["families"]) + return set() + + +def get_version_by_name(project_name, subset_id, version, fields=None): + conn = _get_project_connection(project_name) + query_filter = { + "type": "version", + "parent": _convert_id(subset_id), + "name": version + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_version(project_name, version_id, fields=None): + if not version_id: + return None + conn = _get_project_connection(project_name) + query_filter = { + "type": {"$in": ["version", "hero_version"]}, + "_id": _convert_id(version_id) + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def _get_versions( + project_name, + subset_ids=None, + version_ids=None, + standard=True, + hero=False, + fields=None +): + version_types = [] + if standard: + version_types.append("version") + + if hero: + version_types.append("hero_version") + + if not version_types: + return [] + elif len(version_types) == 1: + query_filter = {"type": version_types[0]} + else: + query_filter = {"type": {"$in": version_types}} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["parent"] = {"$in": subset_ids} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["_id"] = {"$in": version_ids} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_versions( + project_name, + subset_ids=None, + version_ids=None, + hero=False, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=True, + hero=hero, + fields=fields + ) + + +def get_hero_version( + project_name, + subset_id=None, + version_id=None, + fields=None +): + if not subset_id and not version_id: + return None + + subset_ids = None + if subset_id is not None: + subset_ids = [subset_id] + + version_ids = None + if version_id is not None: + version_ids = [version_id] + + versions = list(_get_versions( + project_name, + subset_ids=subset_ids, + version_ids=version_ids, + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_versions( + project_name, + subset_ids=None, + version_ids=None, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=False, + hero=True, + fields=fields + ) + + +def get_version_links(project_name, version_id, fields=None): + conn = _get_project_connection(project_name) + # Does make sense to look for hero versions? + query_filter = { + "type": "version", + "data.inputLinks.input": _convert_id(version_id) + } + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_last_versions(project_name, subset_ids, fields=None): + """Retrieve all latest versions for entered subset_ids. + + Args: + subset_ids (list): List of subset ids. + + Returns: + dict: Key is subset id and value is last version name. + """ + + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return {} + + _pipeline = [ + # Find all versions of those subsets + {"$match": { + "type": "version", + "parent": {"$in": subset_ids} + }}, + # Sorting versions all together + {"$sort": {"name": 1}}, + # Group them by "parent", but only take the last + {"$group": { + "_id": "$parent", + "_version_id": {"$last": "$_id"} + }} + ] + + conn = _get_project_connection(project_name) + version_ids = [ + doc["_version_id"] + for doc in conn.aggregate(_pipeline) + ] + version_docs = get_versions( + project_name, version_ids=version_ids, fields=fields + ) + + return { + version_doc["parent"]: version_doc + for version_doc in version_docs + } + + +def get_last_version_for_subset( + project_name, subset_id=None, subset_name=None, asset_id=None, fields=None +): + subset_doc = get_subset( + project_name, + subset_id=subset_id, + subset_name=subset_name, + asset_id=asset_id, + fields=["_id"] + ) + if not subset_doc: + return None + subset_id = subset_doc["_id"] + last_versions = get_last_versions( + project_name, subset_ids=[subset_id], fields=fields + ) + return last_versions.get(subset_id) + + +def get_representation( + project_name, + representation_id=None, + representation_name=None, + version_id=None, + fields=None +): + if not representation_id: + if not representation_name or not version_id: + return None + + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types} + } + if representation_id is not None: + query_filter["_id"] = _convert_id(representation_id) + + if representation_name is not None: + query_filter["name"] = representation_name + + if version_id is not None: + query_filter["parent"] = version_id + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representation_by_name( + project_name, representation_name, version_id, fields=None +): + if not version_id or not representation_name: + return None + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types}, + "name": representation_name, + "parent": _convert_id(version_id) + } + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + check_site_name=False, + archived=False, + fields=None +): + repre_types = ["representation"] + if archived: + repre_types.append("archived_representations") + if len(repre_types) == 1: + query_filter = {"type": repre_types[0]} + else: + query_filter = {"type": {"$in": repre_types}} + + if check_site_name: + query_filter["files.site.name"] = {"$exists": True} + + if representation_ids is not None: + representation_ids = _convert_ids(representation_ids) + if not representation_ids: + return [] + query_filter["_id"] = {"$in": representation_ids} + + if representation_names is not None: + if not representation_names: + return [] + query_filter["name"] = {"$in": list(representation_names)} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["parent"] = {"$in": version_ids} + + if extensions is not None: + if not extensions: + return [] + query_filter["context.ext"] = {"$in": list(extensions)} + + if names_by_version_ids is not None: + or_query = [] + for version_id, names in names_by_version_ids.items(): + if version_id and names: + or_query.append({ + "parent": _convert_id(version_id), + "name": {"$in": list(names)} + }) + if not or_query: + return [] + query_filter["$or"] = or_query + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_representations_parents(project_name, representations): + repres_by_version_id = collections.defaultdict(list) + versions_by_version_id = {} + versions_by_subset_id = collections.defaultdict(list) + subsets_by_subset_id = {} + subsets_by_asset_id = collections.defaultdict(list) + for representation in representations: + repre_id = representation["_id"] + version_id = representation["parent"] + repres_by_version_id[version_id].append(representation) + + versions = get_versions( + project_name, version_ids=repres_by_version_id.keys() + ) + for version in versions: + version_id = version["_id"] + subset_id = version["parent"] + versions_by_version_id[version_id] = version + versions_by_subset_id[subset_id].append(version) + + subsets = get_subsets( + project_name, subset_ids=versions_by_subset_id.keys() + ) + for subset in subsets: + subset_id = subset["_id"] + asset_id = subset["parent"] + subsets_by_subset_id[subset_id] = subset + subsets_by_asset_id[asset_id].append(subset) + + assets = get_assets(project_name, asset_ids=subsets_by_asset_id.keys()) + assets_by_id = { + asset["_id"]: asset + for asset in assets + } + + project = get_project(project_name) + + output = {} + for version_id, representations in repres_by_version_id.items(): + asset = None + subset = None + version = versions_by_version_id.get(version_id) + if version: + subset_id = version["parent"] + subset = subsets_by_subset_id.get(subset_id) + if subset: + asset_id = subset["parent"] + asset = assets_by_id.get(asset_id) + + for representation in representations: + repre_id = representation["_id"] + output[repre_id] = (version, subset, asset, project) + return output + + +def get_thumbnail_id_from_source(project_name, src_type, src_id): + if not src_type or not src_id: + return None + + query_filter = {"_id": _convert_id(src_id)} + + conn = _get_project_connection(project_name) + src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) + if src_doc: + return src_doc.get("data", {}).get("thumbnail_id") + return None + + +def get_thumbnail(project_name, thumbnail_id, fields=None): + if not thumbnail_id: + return None + query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +""" +Custom data storage: +- Webpublisher - jobs +- Ftrack - events + +openpype/tools/assetlinks/widgets.py +- SimpleLinkView + Query: + - get_versions + - get_subsets + - get_assets + - get_version_links + +openpype/tools/creator/window.py +- CreatorWindow + Query: + - get_asset + - get_subsets + +openpype/tools/launcher/models.py +- LauncherModel + Query: + - get_project + - get_assets + +openpype/tools/libraryloader/app.py +- LibraryLoaderWindow + Query: + - get_project + +openpype/tools/loader/app.py +- LoaderWindow + Query: + - get_project +- show + Query: + - get_projects + +openpype/tools/loader/model.py +- SubsetsModel + Query: + - get_assets + - get_subsets + - get_last_versions + - get_versions + - get_hero_versions + - get_version_by_name +- RepresentationModel + Query: + - get_representations + - sync server specific queries (separated into multiple functions?) + - NOT REPLACED + +openpype/tools/loader/widgets.py +- FamilyModel + Query: + - get_subset_families +- VersionTextEdit + Query: + - get_subset + - get_version +- SubsetWidget + Query: + - get_subsets + - get_representations +- RepresentationWidget + Query: + - get_subsets + - get_versions + - get_representations +- ThumbnailWidget + Query: + - get_thumbnail_id_from_source + - get_thumbnail + +openpype/tools/mayalookassigner/app.py +- MayaLookAssignerWindow + Query: + - get_last_version_for_subset + +openpype/tools/mayalookassigner/commands.py +- create_items_from_nodes + Query: + - get_asset + +openpype/tools/mayalookassigner/vray_proxies.py +- get_look_relationships + Query: + - get_representation_by_name +- load_look + Query: + - get_representation_by_name +- vrayproxy_assign_look + Query: + - get_last_version_for_subset + +openpype/tools/project_manager/project_manager/model.py +- HierarchyModel + Query: + - get_asset_ids_with_subsets + - get_project + - get_assets + +openpype/tools/project_manager/project_manager/view.py +- ProjectDocCache + Query: + - get_project + +openpype/tools/project_manager/project_manager/widgets.py +- CreateProjectDialog + Query: + - get_projects + +openpype/tools/publisher/widgets/create_dialog.py +- CreateDialog + Query: + - get_asset + - get_subsets + +openpype/tools/publisher/control.py +- AssetDocsCache + Query: + - get_assets + +openpype/tools/sceneinventory/model.py +- InventoryModel + Query: + - get_asset + - get_subset + - get_version + - get_last_version_for_subset + - get_representation + +openpype/tools/sceneinventory/switch_dialog.py +- SwitchAssetDialog + Query: + - get_asset + - get_assets + - get_subset + - get_subsets + - get_versions + - get_hero_versions + - get_last_versions + - get_representations + +openpype/tools/sceneinventory/view.py +- SceneInventoryView + Query: + - get_version + - get_versions + - get_hero_versions + - get_representation + - get_representations + +openpype/tools/standalonepublish/widgets/model_asset.py +- AssetModel + Query: + - get_assets + +openpype/tools/standalonepublish/widgets/widget_asset.py +- AssetWidget + Query: + - get_project + - get_asset + +openpype/tools/standalonepublish/widgets/widget_family.py +- FamilyWidget + Query: + - get_asset + - get_subset + - get_subsets + - get_last_version_for_subset + +openpype/tools/standalonepublish/app.py +- Window + Query: + - get_asset + +openpype/tools/texture_copy/app.py +- TextureCopy + Query: + - get_project + - get_asset + +openpype/tools/workfiles/files_widget.py +- FilesWidget + Query: + - get_asset + +openpype/tools/workfiles/model.py +- PublishFilesModel + Query: + - get_subsets + - get_versions + - get_representations + +openpype/tools/workfiles/save_as_dialog.py +- build_workfile_data + Query: + - get_project + - get_asset + +openpype/tools/workfiles/window.py +- Window + Query: + - get_asset + +openpype/tools/utils/assets_widget.py +- AssetModel + Query: + - get_project + - get_assets + +openpype/tools/utils/delegates.py +- VersionDelegate + Query: + - get_versions + - get_hero_versions + +openpype/tools/utils/lib.py +- GroupsConfig + Query: + - get_project +- FamilyConfigCache + Query: + - get_asset + +openpype/tools/utils/tasks_widget.py +- TasksModel + Query: + - get_project + - get_asset +""" From 7109d932ec2ed70a2042d2df7563de368293a88e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:08:42 +0200 Subject: [PATCH 070/258] replace queries in tools --- openpype/tools/assetlinks/widgets.py | 159 +++-- openpype/tools/creator/window.py | 21 +- openpype/tools/launcher/models.py | 21 +- openpype/tools/libraryloader/app.py | 57 +- openpype/tools/loader/app.py | 17 +- openpype/tools/loader/model.py | 289 +++++---- openpype/tools/loader/widgets.py | 231 ++++--- openpype/tools/mayalookassigner/app.py | 21 +- openpype/tools/mayalookassigner/commands.py | 18 +- .../tools/mayalookassigner/vray_proxies.py | 77 +-- .../project_manager/project_manager/model.py | 42 +- .../project_manager/project_manager/view.py | 9 +- .../project_manager/widgets.py | 13 +- openpype/tools/publisher/control.py | 9 +- .../tools/publisher/widgets/create_dialog.py | 22 +- openpype/tools/sceneinventory/model.py | 38 +- .../tools/sceneinventory/switch_dialog.py | 569 ++++++++---------- openpype/tools/sceneinventory/view.py | 113 ++-- openpype/tools/standalonepublish/app.py | 18 +- .../standalonepublish/widgets/model_asset.py | 12 +- .../standalonepublish/widgets/widget_asset.py | 27 +- .../widgets/widget_family.py | 81 ++- openpype/tools/texture_copy/app.py | 28 +- openpype/tools/utils/assets_widget.py | 19 +- openpype/tools/utils/delegates.py | 40 +- openpype/tools/utils/lib.py | 14 +- openpype/tools/utils/tasks_widget.py | 13 +- openpype/tools/workfiles/files_widget.py | 5 +- openpype/tools/workfiles/model.py | 48 +- openpype/tools/workfiles/save_as_dialog.py | 30 +- openpype/tools/workfiles/window.py | 23 +- 31 files changed, 1010 insertions(+), 1074 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 22e8848a60..5ce2a835ef 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -1,10 +1,16 @@ +import collections +from openpype.client import ( + get_versions, + get_subsets, + get_assets, + get_version_links, +) from Qt import QtWidgets class SimpleLinkView(QtWidgets.QWidget): - - def __init__(self, dbcon, parent=None): + def __init__(self, dbcon, parent): super(SimpleLinkView, self).__init__(parent=parent) self.dbcon = dbcon @@ -24,6 +30,11 @@ class SimpleLinkView(QtWidgets.QWidget): self._in_view = in_view self._out_view = out_view + self._version_doc_to_process = None + + @property + def project_name(self): + return self.dbcon.current_project() def clear(self): self._in_view.clear() @@ -31,60 +42,114 @@ class SimpleLinkView(QtWidgets.QWidget): def set_version(self, version_doc): self.clear() - if not version_doc or not self.isVisible(): - return + self._version_doc_to_process = version_doc + if version_doc and self.isVisible(): + self._fill_values() - # inputs - # + def showEvent(self, event): + super(SimpleLinkView, self).showEvent(event) + self._fill_values() + + def _fill_values(self): + if self._version_doc_to_process is None: + return + version_doc = self._version_doc_to_process + self._version_doc_to_process = None + self._fill_inputs(version_doc) + self._fill_outputs(version_doc) + + def _fill_inputs(self, version_doc): + version_ids = set() for link in version_doc["data"].get("inputLinks", []): # Backwards compatibility for "input" key used as "id" if "id" not in link: link_id = link["input"] else: link_id = link["id"] - version = self.dbcon.find_one( - {"_id": link_id, "type": "version"}, - projection={"name": 1, "parent": 1} - ) - if not version: - continue - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + version_ids.add(link_id) - self._in_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + version_docs = list(get_versions( + self.project_name, + version_ids=version_ids, + fields=["name", "parent"] + )) + + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] )) - # outputs - # - outputs = self.dbcon.find( - {"type": "version", "data.inputLinks.input": version_doc["_id"]}, - projection={"name": 1, "parent": 1} - ) - for version in outputs or []: - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) - self._out_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._in_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) + + def _fill_outputs(self, version_doc): + version_docs = list(get_version_links( + self.project_name, + version_doc["_id"], + fields=["name", "parent"] + )) + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] + )) + + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) + + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] + )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._out_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index e0c329fb78..a85f47a060 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,6 +4,7 @@ import re from Qt import QtWidgets, QtCore +from openpype.client import get_asset, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -215,12 +216,12 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return + project_name = legacy_io.active_project() asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = legacy_io.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin @@ -235,7 +236,6 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return - project_name = legacy_io.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = legacy_io.Session["AVALON_TASK"] @@ -269,14 +269,13 @@ class CreatorWindow(QtWidgets.QDialog): self._subset_name_input.setText(subset_name) # Get all subsets of the current asset - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } existing_subset_names_low = set( _name.lower() for _name in existing_subset_names diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 13567e7916..307f591d96 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -9,6 +9,10 @@ import appdirs from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.lib import JSONSettingRegistry from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, @@ -81,13 +85,11 @@ class ActionModel(QtGui.QStandardItemModel): def get_application_actions(self): actions = [] - if not self.dbcon.Session.get("AVALON_PROJECT"): + if not self.dbcon.current_project(): return actions - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"config.apps": True} - ) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name, fields=["config.apps"]) if not project_doc: return actions @@ -448,7 +450,7 @@ class LauncherModel(QtCore.QObject): @property def project_name(self): """Current project name.""" - return self._dbcon.Session.get("AVALON_PROJECT") + return self._dbcon.current_project() @property def refreshing_assets(self): @@ -649,10 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = list(self._dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + asset_docs = get_assets( + self._last_project_name, fields=list(self._asset_projection.keys()) + ) if not self._refreshing_assets: return self._refreshing_assets = False diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 7fda6bd6f9..5f4d10d796 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -3,6 +3,7 @@ import sys from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.client import get_project from openpype.pipeline import AvalonMongoDB from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( @@ -303,14 +304,26 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families = self._subsets_widget.get_subsets_families() self._families_filter_view.set_enabled_families(families) - def set_context(self, context, refresh=True): - self.echo("Setting context: {}".format(context)) - lib.schedule( - lambda: self._set_context(context, refresh=refresh), - 50, channel="mongo" - ) - # ------------------------------ + def set_context(self, context, refresh=True): + """Set the selection in the interface using a context. + The context must contain `asset` data by name. + + Args: + context (dict): The context to apply. + Returns: + None + """ + + asset_name = context.get("asset", None) + if asset_name is None: + return + + if refresh: + self._refresh_assets() + + self._assets_widget.select_asset_by_name(asset_name) + def _on_family_filter_change(self, families): self._subsets_widget.set_family_filters(families) @@ -323,10 +336,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): """Load assets from database""" if self.current_project is not None: # Ensure a project is loaded - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"type": 1} - ) + project_doc = get_project(self.current_project, fields=["_id"]) assert project_doc, "This is a bug" self._families_filter_view.set_enabled_families(set()) @@ -371,7 +381,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Clear the version information on asset change self._version_info_widget.set_version(None) - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self.data["state"]["assetIds"] = asset_ids @@ -426,34 +436,17 @@ class LibraryLoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + src_type = "version" if not thumbnail_src_ids: + src_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(src_type, thumbnail_src_ids) version_ids = [doc["_id"] for doc in version_docs or []] if self._repres_widget: self._repres_widget.set_version_ids(version_ids) - def _set_context(self, context, refresh=True): - """Set the selection in the interface using a context. - The context must contain `asset` data by name. - - Args: - context (dict): The context to apply. - Returns: - None - """ - - asset_name = context.get("asset", None) - if asset_name is None: - return - - if refresh: - self._refresh_assets() - - self._assets_widget.select_asset_by_name(asset_name) - def _on_message_timeout(self): self._message_label.setText("") diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index bb589c199d..1917f23c60 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -3,6 +3,7 @@ import traceback from Qt import QtWidgets, QtCore +from openpype.client import get_projects, get_project from openpype import style from openpype.lib import register_event_callback from openpype.pipeline import ( @@ -39,7 +40,7 @@ class LoaderWindow(QtWidgets.QDialog): def __init__(self, parent=None): super(LoaderWindow, self).__init__(parent) title = "Asset Loader 2.1" - project_name = legacy_io.Session.get("AVALON_PROJECT") + project_name = legacy_io.active_project() if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) @@ -274,8 +275,9 @@ class LoaderWindow(QtWidgets.QDialog): """Load assets from database""" # Ensure a project is loaded - project = legacy_io.find_one({"type": "project"}, {"type": 1}) - assert project, "Project was not found! This is a bug" + project_name = legacy_io.active_project() + project_doc = get_project(project_name, fields=["_id"]) + assert project_doc, "Project was not found! This is a bug" self._assets_widget.refresh() self._assets_widget.setFocus() @@ -314,7 +316,7 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self._version_info_widget.set_version(None) self.data["state"]["assetIds"] = asset_ids @@ -371,10 +373,12 @@ class LoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + source_type = "version" if not thumbnail_src_ids: + source_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(source_type, thumbnail_src_ids) if self._repres_widget is not None: version_ids = [doc["_id"] for doc in version_docs] @@ -576,8 +580,7 @@ def show(debug=False, parent=None, use_context=False): legacy_io.install() any_project = next( - project for project in legacy_io.projects() - if project.get("active", True) is not False + project for project in get_projects(fields=["name"]) ) legacy_io.Session["AVALON_PROJECT"] = any_project["name"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e8e0480d9c 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -7,6 +7,15 @@ from uuid import uuid4 from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, + get_versions, + get_hero_versions, + get_version_by_name, + get_representations +) from openpype.pipeline import ( HeroVersionType, schema, @@ -56,7 +65,7 @@ class BaseRepresentationModel(object): remote_site = remote_provider = None if not project_name: - project_name = self.dbcon.Session.get("AVALON_PROJECT") + project_name = self.dbcon.active_project() else: self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -254,57 +263,61 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # because it also updates the information in other columns if index.column() == self.columns_index["version"]: item = index.internalPointer() - parent = item["_id"] + subset_id = item["_id"] if isinstance(value, HeroVersionType): - versions = list(self.dbcon.find({ - "type": {"$in": ["version", "hero_version"]}, - "parent": parent - }, sort=[("name", -1)])) - - version = None - last_version = None - for __version in versions: - if __version["type"] == "hero_version": - version = __version - elif last_version is None: - last_version = __version - - if version is not None and last_version is not None: - break - - _version = None - for __version in versions: - if __version["_id"] == version["version_id"]: - _version = __version - break - - version["data"] = _version["data"] - version["name"] = _version["name"] - version["is_from_latest"] = ( - last_version["_id"] == _version["_id"] - ) + version_doc = self._get_hero_version(subset_id) else: - version = self.dbcon.find_one({ - "name": value, - "type": "version", - "parent": parent - }) + project_name = self.dbcon.active_project() + version_doc = get_version_by_name( + project_name, subset_id, value + ) # update availability on active site when version changes - if self.sync_server.enabled and version: - query = self._repre_per_version_pipeline([version["_id"]], - self.active_site, - self.remote_site) + if self.sync_server.enabled and version_doc: + query = self._repre_per_version_pipeline( + [version_doc["_id"]], + self.active_site, + self.remote_site + ) docs = list(self.dbcon.aggregate(query)) if docs: repre = docs.pop() - version["data"].update(self._get_repre_dict(repre)) + version_doc["data"].update(self._get_repre_dict(repre)) - self.set_version(index, version) + self.set_version(index, version_doc) return super(SubsetsModel, self).setData(index, value, role) + def _get_hero_version(self, subset_id): + project_name = self.dbcon.active_project() + version_docs = get_versions( + project_name, subset_ids=[subset_id], hero=True + ) + standard_versions = [] + hero_version_doc = None + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version_doc = version_doc + continue + standard_versions.append(version_doc) + + src_version_id = hero_version_doc["version_id"] + src_version = None + is_from_latest = True + for version_doc in reversed(sorted( + standard_versions, key=lambda item: item["name"] + )): + if version_doc["_id"] == src_version_id: + src_version = version_doc + break + is_from_latest = False + + hero_version_doc["data"] = src_version["data"] + hero_version_doc["name"] = src_version["name"] + hero_version_doc["is_from_latest"] = is_from_latest + return hero_version_doc + def set_version(self, index, version): """Update the version data of the given index. @@ -391,26 +404,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): item["repre_info"] = repre_info def _fetch(self): - asset_docs = self.dbcon.find( - { - "type": "asset", - "_id": {"$in": self._asset_ids} - }, - self.asset_doc_projection + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, + asset_ids=self._asset_ids, + fields=self.asset_doc_projection.keys() ) + asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } subset_docs_by_id = {} - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": {"$in": self._asset_ids} - }, - self.subset_doc_projection + subset_docs = get_subsets( + project_name, + asset_ids=self._asset_ids, + fields=self.subset_doc_projection.keys() ) + subset_families = set() for subset_doc in subset_docs: if self._doc_fetching_stop: @@ -423,37 +435,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_docs_by_id[subset_doc["_id"]] = subset_doc subset_ids = list(subset_docs_by_id.keys()) - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"}, - "type": {"$last": "$type"}, - "data": {"$last": "$data"}, - "locations": {"$last": "$locations"}, - "schema": {"$last": "$schema"} - }} - ] - last_versions_by_subset_id = dict() - for doc in self.dbcon.aggregate(_pipeline): - if self._doc_fetching_stop: - return - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc + last_versions_by_subset_id = get_last_versions( + project_name, + subset_ids, + fields=["_id", "parent", "name", "type", "data", "schema"] + ) - hero_versions = self.dbcon.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - }) + hero_versions = get_hero_versions(project_name, subset_ids=subset_ids) missing_versions = [] for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -462,10 +450,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): missing_versions_by_id = {} if missing_versions: - missing_version_docs = self.dbcon.find({ - "type": "version", - "_id": {"$in": missing_versions} - }) + missing_version_docs = get_versions( + project_name, version_ids=missing_versions + ) missing_versions_by_id = { missing_version_doc["_id"]: missing_version_doc for missing_version_doc in missing_version_docs @@ -488,23 +475,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version - self._doc_payload = { - "asset_docs_by_id": asset_docs_by_id, - "subset_docs_by_id": subset_docs_by_id, - "subset_families": subset_families, - "last_versions_by_subset_id": last_versions_by_subset_id - } - + repre_info = {} if self.sync_server.enabled: version_ids = set() for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - query = self._repre_per_version_pipeline(list(version_ids), - self.active_site, - self.remote_site) + query = self._repre_per_version_pipeline( + list(version_ids), self.active_site, self.remote_site + ) - repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return @@ -512,7 +492,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc - self._doc_payload["repre_info_by_version_id"] = repre_info + self._doc_payload = { + "asset_docs_by_id": asset_docs_by_id, + "subset_docs_by_id": subset_docs_by_id, + "subset_families": subset_families, + "last_versions_by_subset_id": last_versions_by_subset_id, + "repre_info_by_version_id": repre_info + } self.doc_fetched.emit() @@ -1062,6 +1048,16 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "remote_site": "Remote" } + repre_projection = { + "_id": 1, + "name": 1, + "context.subset": 1, + "context.asset": 1, + "context.version": 1, + "context.representation": 1, + 'files.sites': 1 + } + def __init__(self, dbcon, header, version_ids): super(RepresentationModel, self).__init__() self.dbcon = dbcon @@ -1073,22 +1069,22 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): sync_server = active_site = remote_site = None active_provider = remote_provider = None - project = dbcon.Session["AVALON_PROJECT"] - if project: + project_name = dbcon.current_project() + if project_name: sync_server = manager.modules_by_name["sync_server"] - active_site = sync_server.get_active_site(project) - remote_site = sync_server.get_remote_site(project) + active_site = sync_server.get_active_site(project_name) + remote_site = sync_server.get_remote_site(project_name) # TODO refactor - active_provider = \ - sync_server.get_provider_for_site(project, - active_site) + active_provider = sync_server.get_provider_for_site( + project_name, active_site + ) if active_site == 'studio': active_provider = 'studio' - remote_provider = \ - sync_server.get_provider_for_site(project, - remote_site) + remote_provider = sync_server.get_provider_for_site( + project_name, remote_site + ) if remote_site == 'studio': remote_provider = 'studio' @@ -1127,8 +1123,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if index.column() == self.Columns.index("name"): if item.get("isMerged"): return item["icon"] - else: - return self._icons["repre"] + return self._icons["repre"] active_index = self.Columns.index("active_site") remote_index = self.Columns.index("remote_site") @@ -1144,12 +1139,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): # site added, sync in progress progress_str = "not avail." if progress >= 0: - # progress == 0 for isMerged is unavailable if progress == 0 and item.get("isMerged"): progress_str = "not avail." else: - progress_str = "{}% {}".format(int(progress * 100), - label) + progress_str = "{}% {}".format( + int(progress * 100), label + ) return progress_str @@ -1192,7 +1187,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if len(self.version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: - group_item = Item() item_id = str(uuid4()) group_item.update({ @@ -1213,9 +1207,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_groups_items[doc["name"]] = 0 group = group_item - progress = lib.get_progress_for_repre(doc, - self.active_site, - self.remote_site) + progress = lib.get_progress_for_repre( + doc, self.active_site, self.remote_site + ) active_site_icon = self._icons.get(self.active_provider) remote_site_icon = self._icons.get(self.remote_provider) @@ -1248,9 +1242,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'remote_site_progress': progress[self.remote_site] } if group: - group = self._sum_group_progress(doc["name"], group, - current_progress, - repre_groups_items) + group = self._sum_group_progress( + doc["name"], group, current_progress, repre_groups_items + ) self.add_child(item, group) @@ -1269,47 +1263,40 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return self._items_by_id.get(item_id) def refresh(self): - docs = [] - session_project = self.dbcon.Session['AVALON_PROJECT'] - if not session_project: + project_name = self.dbcon.current_project() + if not project_name: return + repre_docs = [] if self.version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python - docs = list(self.dbcon.find( - {"type": "representation", "parent": {"$in": self.version_ids}, - "files.sites.name": {"$exists": 1}}, self.projection())) - self._docs = docs + repre_docs = list(get_representations( + project_name, + version_ids=self.version_ids, + check_site_name=True, + fields=self.repre_projection.keys() + )) + + self._docs = repre_docs self.doc_fetched.emit() - @classmethod - def projection(cls): - return { - "_id": 1, - "name": 1, - "context.subset": 1, - "context.asset": 1, - "context.version": 1, - "context.representation": 1, - 'files.sites': 1 - } + def _sum_group_progress( + self, repre_name, group, current_item_progress, repre_groups_items + ): + """Update final group progress - def _sum_group_progress(self, repre_name, group, current_item_progress, - repre_groups_items): - """ - Update final group progress - Called after every item in group is added + Called after every item in group is added - Args: - repre_name(string) - group(dict): info about group of selected items - current_item_progress(dict): {'active_site_progress': XX, - 'remote_site_progress': YY} - repre_groups_items(dict) - Returns: - (dict): updated group info + Args: + repre_name(string) + group(dict): info about group of selected items + current_item_progress(dict): {'active_site_progress': XX, + 'remote_site_progress': YY} + repre_groups_items(dict) + Returns: + (dict): updated group info """ repre_groups_items[repre_name] += 1 diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..6c7acc593d 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -7,6 +7,16 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.client import ( + get_subset_families, + get_subset, + get_subsets, + get_version, + get_versions, + get_representations, + get_thumbnail_id_from_source, + get_thumbnail, +) from openpype.api import Anatomy from openpype.pipeline import HeroVersionType from openpype.pipeline.thumbnail import get_thumbnail_binary @@ -237,8 +247,7 @@ class SubsetWidget(QtWidgets.QWidget): self.model = model self.view = view - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + self.on_project_change(dbcon.current_project()) view.customContextMenuRequested.connect(self.on_context_menu) @@ -302,33 +311,23 @@ class SubsetWidget(QtWidgets.QWidget): item["version_document"] ) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + project_name = self.dbcon.active_project() + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } version_ids = list(version_docs_by_id.keys()) - repre_docs = self.dbcon.find( - # Query all representations for selected versions at once - { - "type": "representation", - "parent": {"$in": version_ids} - }, - # Query only name and parent from representation - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name", "parent"] ) + repre_docs_by_version_id = { version_id: [] for version_id in version_ids @@ -566,28 +565,42 @@ class SubsetWidget(QtWidgets.QWidget): # same representation available # Trigger - repre_ids = [] + project_name = self.dbcon.active_project() + subset_names_by_version_id = collections.defaultdict(set) for item in items: - representation = self.dbcon.find_one( - { - "type": "representation", - "name": representation_name, - "parent": item["version_document"]["_id"] - }, - {"_id": 1} - ) - if not representation: - self.echo("Subset '{}' has no representation '{}'".format( - item["subset"], representation_name - )) - continue - repre_ids.append(representation["_id"]) + version_id = item["version_document"]["_id"] + subset_names_by_version_id[version_id].add(item["subset"]) + + version_ids = set(subset_names_by_version_id.keys()) + repre_docs = get_representations( + project_name, + representation_names=[representation_name], + version_ids=version_ids, + fields=["_id", "parent"] + ) + + repre_ids = [] + for repre_doc in repre_docs: + repre_ids.append(repre_doc["_id"]) + + version_id = repre_doc["parent"] + if version_id not in version_ids: + version_ids.remove(version_id) + + for version_id in version_ids: + joined_subset_names = ", ".join([ + '"{}"'.format(subset) + for subset in subset_names_by_version_id[version_id] + ]) + self.echo("Subsets {} don't have representation '{}'".format( + joined_subset_names, representation_name + )) # get contexts only for selected menu option repre_contexts = get_repres_contexts(repre_ids, self.dbcon) - options = lib.get_options(action, loader, self, - list(repre_contexts.values())) - + options = lib.get_options( + action, loader, self, list(repre_contexts.values()) + ) error_info = _load_representations_by_loader( loader, repre_contexts, options=options ) @@ -661,27 +674,21 @@ class VersionTextEdit(QtWidgets.QTextEdit): print("Querying..") + project_name = self.dbcon.active_project() if not version_doc: - version_doc = self.dbcon.find_one({ - "_id": version_id, - "type": {"$in": ["version", "hero_version"]} - }) + version_doc = get_version(project_name, version_id=version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = self.dbcon.find_one({ - "_id": version_doc["version_id"], - "type": "version" - }) + _version_doc = get_version( + project_name, version_id=version_doc["version_id"] + ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( _version_doc["name"] ) - subset = self.dbcon.find_one({ - "_id": version_doc["parent"], - "type": "subset" - }) + subset = get_subset(project_name, subset_id=version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp @@ -752,7 +759,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): if not source: return - project_name = self.dbcon.Session["AVALON_PROJECT"] + project_name = self.dbcon.current_project() if self._anatomy is None or self._anatomy.project_name != project_name: self._anatomy = Anatomy(project_name) @@ -833,24 +840,19 @@ class ThumbnailWidget(QtWidgets.QLabel): QtCore.Qt.SmoothTransformation ) - def set_thumbnail(self, doc_id=None): - if not doc_id: + def set_thumbnail(self, src_type, doc_ids): + if not doc_ids: self.set_pixmap() return - if isinstance(doc_id, (list, tuple)): - if len(doc_id) < 1: - self.set_pixmap() - return - doc_id = doc_id[0] + src_id = doc_ids[0] - doc = self.dbcon.find_one( - {"_id": doc_id}, - {"data.thumbnail_id"} + project_name = self.dbcon.active_project() + thumbnail_id = get_thumbnail_id_from_source( + project_name, + src_type, + src_id, ) - thumbnail_id = None - if doc: - thumbnail_id = doc.get("data", {}).get("thumbnail_id") if thumbnail_id == self.current_thumb_id: if self.current_thumbnail is None: self.set_pixmap() @@ -861,9 +863,7 @@ class ThumbnailWidget(QtWidgets.QLabel): self.set_pixmap() return - thumbnail_ent = self.dbcon.find_one( - {"type": "thumbnail", "_id": thumbnail_id} - ) + thumbnail_ent = get_thumbnail(project_name, thumbnail_id) if not thumbnail_ent: return @@ -917,21 +917,9 @@ class FamilyModel(QtGui.QStandardItemModel): def refresh(self): families = set() - if self.dbcon.Session.get("AVALON_PROJECT"): - result = list(self.dbcon.aggregate([ - {"$match": { - "type": "subset" - }}, - {"$project": { - "family": {"$arrayElemAt": ["$data.families", 0]} - }}, - {"$group": { - "_id": "family_group", - "families": {"$addToSet": "$family"} - }} - ])) - if result: - families = set(result[0]["families"]) + project_name = self.dbcon.current_project() + if project_name: + families = get_subset_families(project_name) root_item = self.invisibleRootItem() @@ -1213,8 +1201,8 @@ class RepresentationWidget(QtWidgets.QWidget): self.proxy_model = proxy_model self.sync_server_enabled = False - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + + self.on_project_change(dbcon.current_project()) self.model.refresh() @@ -1243,23 +1231,18 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - repre_docs = list(self.dbcon.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - { - "name": 1, - "parent": 1 - } - )) + project_name = self.dbcon.actual_project() + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["name", "parent"] + ) + version_ids = [ repre_doc["parent"] for repre_doc in repre_docs ] - version_docs = self.dbcon.find({ - "_id": {"$in": version_ids} - }) + version_docs = get_versions(project_name, version_ids=version_ids) version_docs_by_id = {} version_docs_by_subset_id = collections.defaultdict(list) @@ -1269,15 +1252,10 @@ class RepresentationWidget(QtWidgets.QWidget): version_docs_by_id[version_id] = version_doc version_docs_by_subset_id[subset_id].append(version_doc) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc @@ -1446,13 +1424,12 @@ class RepresentationWidget(QtWidgets.QWidget): self._process_action(items, menu, point) def _process_action(self, items, menu, point): - """ - Show the context action menu and process selected + """Show the context action menu and process selected - Args: - items(dict): menu items - menu(OptionalMenu) - point(PointIndex) + Args: + items(dict): menu items + menu(OptionalMenu) + point(PointIndex) """ global_point = self.tree_view.mapToGlobal(point) action = menu.exec_(global_point) @@ -1468,21 +1445,23 @@ class RepresentationWidget(QtWidgets.QWidget): data_by_repre_id = {} selected_side = action_representation.get("selected_side") + is_sync_loader = tools_lib.is_sync_loader(loader) for item in items: - if tools_lib.is_sync_loader(loader): - site_name = "{}_site_name".format(selected_side) - data = { - "_id": item.get("_id"), - "site_name": item.get(site_name), - "project_name": self.dbcon.Session["AVALON_PROJECT"] - } + item_id = item.get("_id") + repre_ids.append(item_id) + if not is_sync_loader: + continue - if not data["site_name"]: - continue + site_name = "{}_site_name".format(selected_side) + data_site_name = item.get(site_name) + if not data_site_name: + continue - data_by_repre_id[data["_id"]] = data - - repre_ids.append(item.get("_id")) + data_by_repre_id[item_id] = { + "_id": item_id, + "site_name": data_site_name, + "project_name": self.dbcon.active_project() + } repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 1b6cad77a8..427edf8245 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,6 +4,7 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import get_last_version_for_subset from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -211,6 +212,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): selection = self.assign_selected.isChecked() asset_nodes = self.asset_outliner.get_nodes(selection=selection) + project_name = legacy_io.active_project() start = time.time() for i, (asset, item) in enumerate(asset_nodes.items()): @@ -222,23 +224,20 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): assign_look = next((subset for subset in item["looks"] if subset["name"] in looks), None) if not assign_look: - self.echo("{} No matching selected " - "look for {}".format(prefix, asset)) + self.echo( + "{} No matching selected look for {}".format(prefix, asset) + ) continue # Get the latest version of this asset's look subset - version = legacy_io.find_one( - { - "type": "version", - "parent": assign_look["_id"] - }, - sort=[("name", -1)] + version = get_last_version_for_subset( + project_name, subset_id=assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] - self.echo("{} Assigning {} to {}\t".format(prefix, - subset_name, - asset)) + self.echo("{} Assigning {} to {}\t".format( + prefix, subset_name, asset + )) nodes = item["nodes"] if cmds.pluginInfo('vrayformaya', query=True, loaded=True): diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index d41d8ca5a2..a4fc1fab70 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -2,9 +2,9 @@ from collections import defaultdict import logging import os -from bson.objectid import ObjectId import maya.cmds as cmds +from openpype.client import get_asset from openpype.pipeline import ( legacy_io, remove_container, @@ -159,11 +159,9 @@ def create_items_from_nodes(nodes): log.warning("No id hashes") return asset_view_items + project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = legacy_io.find_one( - {"_id": ObjectId(_id)}, - projection={"name": True} - ) + asset = get_asset(project_name, asset_id=_id, fields=["name"]) # Skip if asset id is not found if not asset: @@ -180,10 +178,12 @@ def create_items_from_nodes(nodes): namespace = get_namespace_from_node(node) namespaces.add(namespace) - asset_view_items.append({"label": asset["name"], - "asset": asset, - "looks": looks, - "namespaces": namespaces}) + asset_view_items.append({ + "label": asset["name"], + "asset": asset, + "looks": looks, + "namespaces": namespaces + }) return asset_view_items diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index 3523b24bf3..b2ba21f944 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -6,11 +6,14 @@ import logging import json import six -from bson.objectid import ObjectId import alembic.Abc from maya import cmds +from openpype.client import ( + get_representation_by_name, + get_last_version_for_subset, +) from openpype.pipeline import ( legacy_io, load_container, @@ -155,13 +158,12 @@ def get_look_relationships(version_id): Returns: dict: Dictionary of relations. - """ - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, representation_name="json", version_id=version_id + ) # Load relationships shader_relation = get_representation_path(json_representation) @@ -184,12 +186,12 @@ def load_look(version_id): list of shader nodes. """ + + project_name = legacy_io.active_project() # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + look_representation = get_representation_by_name( + project_name, representation_name="ma", version_id=version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -220,42 +222,6 @@ def load_look(version_id): return shader_nodes -def get_latest_version(asset_id, subset): - # type: (str, str) -> dict - """Get latest version of subset. - - Args: - asset_id (str): Asset ID - subset (str): Subset name. - - Returns: - Latest version - - Throws: - RuntimeError: When subset or version doesn't exist. - - """ - subset = legacy_io.find_one({ - "name": subset, - "parent": ObjectId(asset_id), - "type": "subset" - }) - if not subset: - raise RuntimeError("Subset does not exist: %s" % subset) - - version = legacy_io.find_one( - { - "type": "version", - "parent": subset["_id"] - }, - sort=[("name", -1)] - ) - if not version: - raise RuntimeError("Version does not exist.") - - return version - - def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): # type: (str, str) -> None """Assign look to vray proxy. @@ -281,13 +247,20 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): asset_id = node_id.split(":", 1)[0] node_ids_by_asset_id[asset_id].add(node_id) + project_name = legacy_io.active_project() for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - try: - version = get_latest_version(asset_id, subset=subset) - except RuntimeError as exc: - print(exc) + version = get_last_version_for_subset( + project_name, + subset_name=subset, + asset_id=asset_id, + fields=["_id"] + ) + if not version: + print("Didn't find last version for subset name {}".format( + subset + )) continue relationships = get_look_relationships(version["_id"]) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 223cfa629d..c5bde5aaec 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,6 +7,11 @@ from pymongo import UpdateOne, DeleteOne from Qt import QtCore, QtGui +from openpype.client import ( + get_project, + get_assets, + get_asset_ids_with_subsets, +) from openpype.lib import ( CURRENT_DOC_SCHEMAS, PypeLogger, @@ -255,10 +260,11 @@ class HierarchyModel(QtCore.QAbstractItemModel): return # Find project'd document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - ProjectItem.query_projection + project_doc = get_project( + project_name, + fields=list(ProjectItem.query_projection.keys()) ) + # Skip if project document does not exist # - this shouldn't happen using only UI elements if not project_doc: @@ -269,9 +275,8 @@ class HierarchyModel(QtCore.QAbstractItemModel): self.add_item(project_item) # Query all assets of the project - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - AssetItem.query_projection + asset_docs = get_assets( + project_name, fields=AssetItem.query_projection.keys() ) asset_docs_by_id = { asset_doc["_id"]: asset_doc @@ -282,31 +287,16 @@ class HierarchyModel(QtCore.QAbstractItemModel): # if asset item can be modified (name and hierarchy change) # - the same must be applied to all it's parents asset_ids = list(asset_docs_by_id.keys()) - result = [] + asset_ids_with_subsets = [] if asset_ids: - result = self.dbcon.database[project_name].aggregate([ - { - "$match": { - "type": "subset", - "parent": {"$in": asset_ids} - } - }, - { - "$group": { - "_id": "$parent", - "count": {"$sum": 1} - } - } - ]) + asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name, asset_ids=asset_ids + ) asset_modifiable = { - asset_id: True + asset_id: asset_id not in asset_ids_with_subsets for asset_id in asset_docs_by_id.keys() } - for item in result: - asset_id = item["_id"] - count = item["count"] - asset_modifiable[asset_id] = count < 1 # Store assets by their visual parent to be able create their hierarchy asset_docs_by_parent_id = collections.defaultdict(list) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4b5bc36aeb..e9d2874c54 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,6 +3,7 @@ from queue import Queue from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_project from .delegates import ( NumberDelegate, NameDelegate, @@ -47,12 +48,8 @@ class ProjectDocCache: def set_project(self, project_name): self.project_doc = None - if not project_name: - return - - self.project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"} - ) + if project_name: + self.project_doc = get_project(project_name) class ToolsCache: diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index dc75b30bd7..371d1ba2ef 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,5 +1,6 @@ import re +from openpype.client import get_projects from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX @@ -272,15 +273,9 @@ class CreateProjectDialog(QtWidgets.QDialog): def _get_existing_projects(self): project_names = set() project_codes = set() - for project_name in self.dbcon.database.collection_names(): - # Each collection will have exactly one project document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - {"name": 1, "data.code": 1} - ) - if not project_doc: - continue - + for project_doc in get_projects( + inactive=True, fields=["name", "data.code"] + ): project_name = project_doc.get("name") if not project_name: continue diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2973d6a5bb..915fb7f32e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -13,6 +13,7 @@ except Exception: import pyblish.api +from openpype.client import get_assets from openpype.pipeline import ( PublishValidationError, registered_host, @@ -116,10 +117,10 @@ class AssetDocsCache: def _query(self): if self._asset_docs is None: - asset_docs = list(self.dbcon.find( - {"type": "asset"}, - self.projection - )) + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, fields=self.projection.keys() + ) task_names_by_asset_name = {} for asset_doc in asset_docs: asset_name = asset_doc["name"] diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 9e357f3a56..d579831b21 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -9,6 +9,8 @@ try: except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui + +from openpype.client import get_asset, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -647,21 +649,19 @@ class CreateDialog(QtWidgets.QDialog): if asset_name is None: return - asset_doc = self.dbcon.find_one({ - "type": "asset", - "name": asset_name - }) + project_name = self.dbcon.active_project() + asset_doc = get_asset(project_name, asset_name=asset_name) self._asset_doc = asset_doc if asset_doc: - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"name": 1} + asset_id = asset_doc["_id"] + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - self._subset_names = set(subset_docs.distinct("name")) + self._subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } if not asset_doc: self.subset_name_input.setText("< Asset is not set >") diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8d72020c98..9cf69ed650 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -5,8 +5,14 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome -from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_subset, + get_version, + get_last_version_for_subset, + get_representation, +) from openpype.pipeline import ( legacy_io, schema, @@ -55,7 +61,7 @@ class InventoryModel(TreeModel): if not self.sync_enabled: return - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = legacy_io.current_project() active_site = sync_server.get_active_site(project_name) remote_site = sync_server.get_remote_site(project_name) @@ -291,6 +297,9 @@ class InventoryModel(TreeModel): node.Item: root node which has children added based on the data """ + # NOTE: @iLLiCiTiT this need refactor + project_name = legacy_io.active_project() + self.beginResetModel() # Group by representation @@ -304,32 +313,36 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = legacy_io.find_one({"_id": ObjectId(repre_id)}) + representation = get_representation( + project_name, representation_id=repre_id + ) if not representation: not_found["representation"].append(group_items) not_found_ids.append(repre_id) continue - version = legacy_io.find_one({"_id": representation["parent"]}) + version = get_version( + project_name, version_id=representation["parent"] + ) if not version: not_found["version"].append(group_items) not_found_ids.append(repre_id) continue elif version["type"] == "hero_version": - _version = legacy_io.find_one({ - "_id": version["version_id"] - }) + _version = get_version( + project_name, version_id=version["version_id"] + ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = legacy_io.find_one({"_id": version["parent"]}) + subset = get_subset(project_name, subset_id=version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) continue - asset = legacy_io.find_one({"_id": subset["parent"]}) + asset = get_asset(project_name, asset_id=subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) @@ -390,10 +403,9 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = legacy_io.find_one({ - "type": "version", - "parent": version["parent"] - }, sort=[("name", -1)]) + highest_version = get_last_version_for_subset( + project_name, subset_id=version["parent"] + ) # create the group header group_node = Item() diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b2d770330f..b940c66a56 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -4,6 +4,16 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_assets, + get_subset, + get_subsets, + get_versions, + get_hero_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.pipeline.load import ( discover_loader_plugins, @@ -144,6 +154,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): self._prepare_content_data() self.refresh(True) + def active_project(self): + return legacy_io.active_project() + def _prepare_content_data(self): repre_ids = set() content_loaders = set() @@ -151,10 +164,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_ids.add(ObjectId(item["representation"])) content_loaders.add(item["loader"]) - repres = list(legacy_io.find({ - "type": {"$in": ["representation", "archived_representation"]}, - "_id": {"$in": list(repre_ids)} - })) + project_name = self.active_project() + repres = get_representations( + project_name, + representation_ids=repre_ids, + archived=True + ) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation @@ -179,10 +194,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_repres[repre_id] = repres_by_id[repre_id] version_ids.append(repre["parent"]) - versions = legacy_io.find({ - "type": {"$in": ["version", "hero_version"]}, - "_id": {"$in": list(set(version_ids))} - }) + versions = get_versions( + project_name, + version_ids=set(version_ids), + hero=True + ) content_versions = {} hero_version_ids = set() for version in versions: @@ -198,10 +214,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): else: subset_ids.append(content_versions[version_id]["parent"]) - subsets = legacy_io.find({ - "type": {"$in": ["subset", "archived_subset"]}, - "_id": {"$in": subset_ids} - }) + subsets = get_subsets( + project_name, subset_ids=subset_ids, archived=True + ) subsets_by_id = {sub["_id"]: sub for sub in subsets} asset_ids = [] @@ -220,10 +235,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_ids.append(subset["parent"]) content_subsets[subset_id] = subset - assets = legacy_io.find({ - "type": {"$in": ["asset", "archived_asset"]}, - "_id": {"$in": list(asset_ids)} - }) + assets = get_assets(project_name, asset_ids=asset_ids, archived=True) assets_by_id = {asset["_id"]: asset for asset in assets} missing_assets = [] @@ -472,9 +484,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": True} + asset_doc = get_asset( + self.active_project(), + asset_name=selected_asset, + fields=["_id"] ) if not asset_doc: return [] @@ -523,38 +536,35 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxx( self, asset_doc, selected_subset, selected_repre ): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + subset_name=selected_subset, + asset_id=asset_doc["_id"], + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) if not version_doc: return [] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"], - "name": selected_repre - }, - {"_id": True} + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) if not subset_doc: return [] @@ -563,41 +573,51 @@ class SwitchAssetDialog(QtWidgets.QDialog): for repre_doc in self.content_repres.values(): repre_names.add(repre_doc["name"]) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": subset_doc["_id"], - "name": {"$in": list(repre_names)} - }, - {"_id": True} + # TODO where to take version ids? + version_ids = [] + repre_docs = get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xox(self, asset_doc, selected_repre): - susbet_names = set() + subset_names = set() for subset_doc in self.content_subsets.values(): - susbet_names.add(subset_doc["name"]) + subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "name": {"$in": list(susbet_names)}, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id", "name"] ) - subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": subset_ids}, - "name": selected_repre - }, - {"_id": True} + subset_name_by_id = { + subset_doc["_id"]: subset_doc["name"] + for subset_doc in subset_docs + } + subset_ids = list(subset_name_by_id.keys()) + last_versions_by_subset_id = self.find_last_versions(subset_ids) + last_version_id_by_subset_name = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + subset_name = subset_name_by_id[subset_id] + last_version_id_by_subset_name[subset_name] = ( + last_version["_id"] + ) + + repre_docs = get_representations( + project_name, + version_ids=last_version_id_by_subset_name.values(), + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xoo(self, asset_doc): + project_name = self.active_project() repres_by_subset_name = collections.defaultdict(set) for repre_doc in self.content_repres.values(): repre_name = repre_doc["name"] @@ -606,13 +626,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_name = subset_doc["name"] repres_by_subset_name[subset_name].add(repre_name) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(repres_by_subset_name.keys())} - }, - {"_id": True, "name": True} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=repres_by_subset_name.keys(), + fields=["_id", "name"] )) subset_name_by_id = { subset_doc["_id"]: subset_doc["name"] @@ -627,60 +645,59 @@ class SwitchAssetDialog(QtWidgets.QDialog): last_version["_id"] ) - repre_or_query = [] + repre_names_by_version_id = {} for subset_name, repre_names in repres_by_subset_name.items(): version_id = last_version_id_by_subset_name.get(subset_name) # This should not happen but why to crash? - if version_id is None: - continue - repre_or_query.append({ - "parent": version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - {"$or": repre_or_query}, - {"_id": True} + if version_id is not None: + repre_names_by_version_id[version_id] = list(repre_names) + + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxx( self, selected_subset, selected_repre ): - subset_docs = list(legacy_io.find({ - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - })) + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] last_versions_by_subset_id = self.find_last_versions(subset_ids) last_version_ids = [ last_version["_id"] for last_version in last_versions_by_subset_id.values() ] - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": last_version_ids}, - "name": selected_repre - }) - + repre_docs = get_representations( + project_name, + version_ids=last_version_ids, + representation_names=[selected_repre], + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxo(self, selected_subset): - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": True, "parent": True} - )) - if not subset_docs: - return list() - + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] + ) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } + if not subset_docs: + return list() + last_versions_by_subset_id = self.find_last_versions( subset_docs_by_id.keys() ) @@ -702,56 +719,44 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_id = asset_doc["_id"] repre_names_by_asset_id[asset_id].add(repre_name) - repre_or_query = [] + repre_names_by_version_id = {} for last_version_id, subset_id in subset_id_by_version_id.items(): subset_doc = subset_docs_by_id[subset_id] asset_id = subset_doc["parent"] repre_names = repre_names_by_asset_id.get(asset_id) if not repre_names: continue - repre_or_query.append({ - "parent": last_version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - { - "type": "representation", - "$or": repre_or_query - }, - {"_id": True} - ) + repre_names_by_version_id[last_version_id] = repre_names + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oox(self, selected_repre): - repre_docs = legacy_io.find( - { - "name": selected_repre, - "parent": {"$in": list(self.content_versions.keys())} - }, - {"_id": True} + project_name = self.active_project() + repre_docs = get_representations( + project_name, + representation_names=[selected_repre], + version_ids=self.content_versions.keys(), + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_asset_box_values(self): - asset_docs = legacy_io.find( - {"type": "asset"}, - {"_id": 1, "name": 1} - ) + project_name = self.active_project() + asset_docs = get_assets(project_name, fields=["_id", "name"]) asset_names_by_id = { asset_doc["_id"]: asset_doc["name"] for asset_doc in asset_docs } - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(asset_names_by_id.keys())} - }, - { - "parent": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_names_by_id.keys(), + fields=["parent"] ) - filtered_assets = [] for subset in subsets: asset_name = asset_names_by_id[subset["parent"]] @@ -760,25 +765,20 @@ class SwitchAssetDialog(QtWidgets.QDialog): return sorted(filtered_assets) def _get_subset_box_values(self): + project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] + ) asset_ids = [asset_doc["_id"]] else: asset_ids = list(self.content_assets.keys()) - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": asset_ids} - }, - { - "parent": 1, - "name": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_ids, + fields=["parent", "name"] ) subset_names_by_parent_id = collections.defaultdict(set) @@ -800,6 +800,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _representations_box_values(self): # NOTE hero versions are not used because it is expected that # hero version has same representations as latests + project_name = self.active_project() selected_asset = self._assets_box.currentText() selected_subset = self._subsets_box.currentText() @@ -807,16 +808,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [ ] [?] if not selected_asset and not selected_subset: # Find all representations of selection's subsets - possible_repres = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(self.content_versions.keys())} - }, - { - "parent": 1, - "name": 1 - } - )) + possible_repres = get_representations( + project_name, + version_ids=self.content_versions.keys(), + fields=["parent", "name"] + ) possible_repres_by_parent = collections.defaultdict(set) for repre in possible_repres: @@ -836,29 +832,23 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"] - }, - { - "name": 1 - } + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + fields=["name"] ) return [ repre_doc["name"] @@ -868,9 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -879,13 +868,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_names = set() for subset_doc in self.content_subsets.values(): subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(subset_names)} - }, - {"_id": 1} + + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id"] ) subset_ids = [ subset_doc["_id"] @@ -903,15 +891,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = list(get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] )) if not repre_docs: return list() @@ -933,13 +916,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): return list(available_repres) # [ ] [x] [?] - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "parent": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] )) if not subset_docs: return list() @@ -960,16 +941,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } - )) + repre_docs = list( + get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] + ) + ) if not repre_docs: return list() @@ -1016,14 +994,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [ ] [?] - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + project_name = self.active_project() + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = legacy_io.find( - {"type": "subset", "parent": asset_doc["_id"]}, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["name"] ) + subset_names = set( subset_doc["name"] for subset_doc in subset_docs @@ -1035,27 +1013,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): break def find_last_versions(self, subset_ids): - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": list(subset_ids)} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "type": {"$last": "$type"} - }} - ] - last_versions_by_subset_id = dict() - for doc in legacy_io.aggregate(_pipeline): - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc - return last_versions_by_subset_id + project_name = self.active_project() + return get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "type"] + ) def _is_repre_ok(self, validation_state): selected_asset = self._assets_box.get_valid_value() @@ -1078,33 +1041,28 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [x] [ ] + project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) - last_versions_by_subset_id = self.find_last_versions( - [subset_doc["_id"]] - ) - last_version = last_versions_by_subset_id.get(subset_doc["_id"]) + subset_id = subset_doc["_id"] + last_versions_by_subset_id = self.find_last_versions([subset_id]) + last_version = last_versions_by_subset_id.get(subset_id) if not last_version: validation_state.repre_ok = False return - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": last_version["_id"] - }, - {"name": 1} + repre_docs = get_representations( + project_name, + version_ids=[last_version["_id"]], + fields=["name"] ) repre_names = set( @@ -1119,16 +1077,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"_id": 1, "name": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "name"] )) subset_name_by_id = {} @@ -1145,15 +1100,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_subset_name = {} for repre_doc in repre_docs: @@ -1176,15 +1126,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [x] [ ] # Subset documents - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "name": 1, "parent": 1} + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "name", "parent"] ) - subset_docs_by_id = {} for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc @@ -1197,15 +1144,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_asset_id = {} for repre_doc in repre_docs: @@ -1245,11 +1187,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_subset = self._subsets_box.get_valid_value() selected_representation = self._representations_box.get_valid_value() + project_name = self.active_project() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset(project_name, asset_name=selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets @@ -1259,16 +1199,15 @@ class SwitchAssetDialog(QtWidgets.QDialog): for asset_doc in asset_docs_by_id.values() } - asset_ids = list(asset_docs_by_id.keys()) - - subset_query = { - "type": "subset", - "parent": {"$in": asset_ids} - } + subset_names = None if selected_subset: - subset_query["name"] = selected_subset + subset_names = [selected_subset] - subset_docs = list(legacy_io.find(subset_query)) + subset_docs = list(get_subsets( + project_name, + subset_names=subset_names, + asset_ids=asset_docs_by_id.keys() + )) subset_ids = [] subset_docs_by_parent_and_name = collections.defaultdict(dict) for subset in subset_docs: @@ -1278,15 +1217,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_docs_by_parent_and_name[parent_id][name] = subset # versions - version_docs = list(legacy_io.find({ - "type": "version", - "parent": {"$in": subset_ids} - }, sort=[("name", -1)])) + _version_docs = get_versions(project_name, subset_ids=subset_ids) + version_docs = list(reversed( + sorted(_version_docs, key=lambda item: item["name"]) + )) - hero_version_docs = list(legacy_io.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - })) + hero_version_docs = list(get_hero_versions( + project_name, subset_ids=subset_ids + )) version_ids = list() @@ -1303,10 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): parent_id = hero_version_doc["parent"] hero_version_docs_by_parent_id[parent_id] = hero_version_doc - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) + repre_docs = get_representations(project_name, version_ids=version_ids) repre_docs_by_parent_id_by_name = collections.defaultdict(dict) for repre_doc in repre_docs: parent_id = repre_doc["parent"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 448e3f4e6f..6a95ccb57b 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -6,6 +6,13 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_version, + get_versions, + get_hero_versions, + get_representation, + get_representations, +) from openpype import style from openpype.pipeline import ( legacy_io, @@ -83,12 +90,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, representaion_ids=repre_ids, fields=["parent"] ) version_ids = [] @@ -97,10 +101,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if version_id not in version_ids: version_ids.append(version_id) - loaded_versions = legacy_io.find({ - "_id": {"$in": version_ids}, - "type": {"$in": ["version", "hero_version"]} - }) + loaded_versions = get_versions( + project_name, version_ids=version_ids, hero=True + ) loaded_hero_versions = [] versions_by_parent_id = collections.defaultdict(list) @@ -114,10 +117,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if parent_id not in version_parents: version_parents.append(parent_id) - all_versions = legacy_io.find({ - "type": {"$in": ["hero_version", "version"]}, - "parent": {"$in": version_parents} - }) + all_versions = get_versions( + project_name, subset_ids=version_parents, hero=True + ) hero_versions = [] versions = [] for version in all_versions: @@ -150,12 +152,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["parent"] ) version_ids = [] @@ -165,13 +165,13 @@ class SceneInventoryView(QtWidgets.QTreeView): version_id_by_repre_id[repre_doc["_id"]] = version_id if version_id not in version_ids: version_ids.append(version_id) - hero_versions = legacy_io.find( - { - "_id": {"$in": version_ids}, - "type": "hero_version" - }, - {"version_id": 1} + + hero_versions = get_hero_versions( + project_name, + version_ids=version_ids, + fields=["version_id"] ) + version_ids = set() for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -183,12 +183,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if current_version_id == hero_version_id: version_id_by_repre_id[_repre_id] = version_id - version_docs = legacy_io.find( - { - "_id": {"$in": list(version_ids)}, - "type": "version" - }, - {"name": 1} + version_docs = get_versions( + project_name, + version_ids=version_ids, + fields=["name"] ) version_name_by_id = {} for version_doc in version_docs: @@ -370,10 +368,9 @@ class SceneInventoryView(QtWidgets.QTreeView): active_site = self.sync_server.get_active_site(project_name) remote_site = self.sync_server.get_remote_site(project_name) - repre_docs = legacy_io.find({ - "type": "representation", - "_id": {"$in": repre_ids} - }) + repre_docs = get_representations( + project_name, representation_ids=repre_ids + ) repre_docs_by_id = { repre_doc["_id"]: repre_doc for repre_doc in repre_docs @@ -658,25 +655,37 @@ class SceneInventoryView(QtWidgets.QTreeView): active = items[-1] + project_name = legacy_io.active_project() # Get available versions for active representation representation_id = ObjectId(active["representation"]) - representation = legacy_io.find_one({"_id": representation_id}) - version = legacy_io.find_one({ - "_id": representation["parent"] - }) - versions = list(legacy_io.find( - { - "parent": version["parent"], - "type": "version" - }, - sort=[("name", 1)] + repre_doc = get_representation( + project_name, + representation_id=representation_id, + fields=["parent"] + ) + + repre_version_doc = get_version( + project_name, + version_id=repre_doc["parent"], + fields=["parent"] + ) + + version_docs = get_versions( + project_name, + subset_ids=[repre_version_doc["parent"]], + hero=True + ) + hero_version = None + standard_versions = [] + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version = version_doc + else: + standard_versions.append(version_doc) + versions = list(reversed( + sorted(standard_versions, key=lambda item: item["name"]) )) - - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) if hero_version: _version_id = hero_version["version_id"] for _version in versions: @@ -703,7 +712,7 @@ class SceneInventoryView(QtWidgets.QTreeView): all_versions = [] if hero_version: all_versions.append(hero_version) - all_versions.extend(reversed(versions)) + all_versions.extend(versions) if current_item: index = all_versions.index(current_item) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 1ad5cd119e..4831db038c 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,6 +6,8 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_asset + from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget ) @@ -126,17 +128,6 @@ class Window(QtWidgets.QDialog): if event: super().resizeEvent(event) - def get_avalon_parent(self, entity): - ''' Avalon DB entities helper - get all parents (exclude project). - ''' - parent_id = entity['data']['visualParent'] - parents = [] - if parent_id is not None: - parent = self.db.find_one({'_id': parent_id}) - parents.extend(self.get_avalon_parent(parent)) - parents.append(parent['name']) - return parents - def on_project_change(self, project_name): self.widget_family.refresh() @@ -152,7 +143,10 @@ class Window(QtWidgets.QDialog): ] if len(selected) == 1: self.valid_parent = True - asset = self.db.find_one({"_id": selected[0], "type": "asset"}) + project_name = self.db.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["name"] + ) self.widget_family.change_asset(asset['name']) else: self.valid_parent = False diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 02e9073555..abfc0a2145 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -4,6 +4,7 @@ import collections from Qt import QtCore, QtGui import qtawesome +from openpype.client import get_assets from openpype.style import ( get_default_entity_icon_color, get_deprecated_entity_font_color, @@ -104,17 +105,18 @@ class AssetModel(TreeModel): def refresh(self): """Refresh the data for the model.""" + project_name = self.dbcon.active_project() self.clear() - if ( - self.dbcon.active_project() is None or - self.dbcon.active_project() == '' - ): + if not project_name: return self.beginResetModel() # Get all assets in current project sorted by name - db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1) + asset_docs = get_assets(project_name) + db_assets = list( + sorted(asset_docs, key=lambda item: item["name"]) + ) # Group the assets by their visual parent's id assets_by_parent = collections.defaultdict(list) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 8b43cd7cf8..0b5802ed9e 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,10 @@ import contextlib from Qt import QtWidgets, QtCore import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.tools.utils import PlaceholderLineEdit from openpype.style import get_default_tools_icon_color @@ -218,7 +222,8 @@ class AssetWidget(QtWidgets.QWidget): self.view = view def collect_data(self): - project = self.dbcon.find_one({'type': 'project'}) + project_name = self.dbcon.active_project() + project = get_project(project_name, fields=["name"]) asset = self.get_active_asset() try: @@ -241,9 +246,16 @@ class AssetWidget(QtWidgets.QWidget): return ent_parents output = [] - if entity.get('data', {}).get('visualParent', None) is None: + parent_asset_id = entity.get('data', {}).get('visualParent', None) + if parent_asset_id is None: return output - parent = self.dbcon.find_one({'_id': entity['data']['visualParent']}) + + project_name = self.dbcon.active_project() + parent = get_asset( + project_name, + asset_id=parent_asset_id, + fields=["name", "data.visualParent"] + ) output.append(parent['name']) output.extend(self.get_parents(parent)) return output @@ -349,9 +361,10 @@ class AssetWidget(QtWidgets.QWidget): tasks = [] selected = self.get_selected_assets() if len(selected) == 1: - asset = self.dbcon.find_one({ - "_id": selected[0], "type": "asset" - }) + project_name = self.dbcon.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["data.tasks"] + ) if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) @@ -400,7 +413,7 @@ class AssetWidget(QtWidgets.QWidget): # Select mode = selection_model.Select | selection_model.Rows - for index in lib.iter_model_rows( + for index in _iter_model_rows( self.proxy, column=0, include_root=False ): # stop iteration if there are no assets to process diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 08cd45bbf2..ed9f405f38 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -1,14 +1,21 @@ import re from Qt import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole -from . import FamilyDescriptionWidget +from openpype.client import ( + get_asset, + get_subset, + get_subsets, + get_last_version_for_subset, +) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator from openpype.lib import TaskNotSetError from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole +from . import FamilyDescriptionWidget + class FamilyWidget(QtWidgets.QWidget): @@ -180,12 +187,9 @@ class FamilyWidget(QtWidgets.QWidget): asset_doc = None if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name - asset_doc = self.dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - {"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin and family @@ -200,14 +204,13 @@ class FamilyWidget(QtWidgets.QWidget): return # Get the asset from the database which match with the name - asset_doc = self.dbcon.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) if asset_doc and plugin: - project_name = self.dbcon.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = self.dbcon.Session["AVALON_TASK"] @@ -231,14 +234,14 @@ class FamilyWidget(QtWidgets.QWidget): self.input_result.setText("Select task please") # Get all subsets of the current asset - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } # Defaults to dropdown defaults = [] @@ -296,47 +299,37 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + project_name = self.dbcon.active_project() asset_name = self.asset_name subset_name = self.input_result.text() version = 1 asset_doc = None subset_doc = None - versions = None if ( asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = self.dbcon.find_one( - { - 'type': 'asset', - 'name': asset_name - }, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) if asset_doc: - subset_doc = self.dbcon.find_one( - { - 'type': 'subset', - 'parent': asset_doc['_id'], - 'name': subset_name - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + subset_name=subset_name, + asset_id=asset_doc['_id'], + fields=["_id"] ) if subset_doc: - versions = self.dbcon.find( - { - 'type': 'version', - 'parent': subset_doc['_id'] - }, - {"name": 1} - ).distinct("name") - - if versions: - versions = sorted(versions) - version = int(versions[-1]) + 1 + last_version = get_last_version_for_subset( + project_name, + subset_id=subset_doc["_id"], + fields=["name"] + ) + if last_version: + version = last_version["name"] + 1 self.version_spinbox.setValue(version) diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index fd8d6dc02e..8703f075d3 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,6 +4,7 @@ import click import speedcopy +from openpype.client import get_project, get_asset from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -29,20 +30,6 @@ class TextureCopy: if os.path.splitext(x)[1].lower() in texture_extensions) return textures - def _get_project(self, project_name): - project = legacy_io.find_one({ - 'type': 'project', - 'name': project_name - }) - return project - - def _get_asset(self, asset_name): - asset = legacy_io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - return asset - def _get_destination_path(self, asset, project): project_name = project["name"] hierarchy = "" @@ -88,11 +75,12 @@ class TextureCopy: t.echo("!!! {}".format(e)) exit(1) - def process(self, asset, project, path): + def process(self, asset_name, project_name, path): """ Process all textures found in path and copy them to asset under project. """ + t.echo(">>> Looking for textures ...") textures = self._get_textures(path) if len(textures) < 1: @@ -101,14 +89,14 @@ class TextureCopy: else: t.echo(">>> Found {} textures ...".format(len(textures))) - project = self._get_project(project) + project = get_project(project_name) if not project: - t.echo("!!! Project name [ {} ] not found.".format(project)) + t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = self._get_asset(asset) - if not project: - t.echo("!!! Asset [ {} ] not found in project".format(asset)) + asset = get_asset(project_name, asset_name=asset_name) + if not asset: + t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) t.echo((">>> Project [ {} ] and " "asset [ {} ] seems to be OK ...").format(project['name'], diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 82bdcd63a2..772946e9e1 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -5,6 +5,10 @@ import Qt from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.style import ( get_objected_colors, get_default_tools_icon_color, @@ -525,21 +529,18 @@ class AssetModel(QtGui.QStandardItemModel): self._doc_fetched.emit() def _fetch_asset_docs(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): + project_name = self.dbcon.current_project() + if not project_name: return [] - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"_id": True} - ) + project_doc = get_project(project_name, fields=["_id"]) if not project_doc: return [] # Get all assets sorted by name - return list(self.dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + return list( + get_assets(project_name, fields=self._asset_projection.keys()) + ) def _stop_fetch_thread(self): self._refreshing = False diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 71f817a1d7..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -6,15 +6,19 @@ import numbers import Qt from Qt import QtWidgets, QtGui, QtCore -from openpype.pipeline import HeroVersionType -from .models import TreeModel -from . import lib - if Qt.__binding__ == "PySide": from PySide.QtGui import QStyleOptionViewItemV4 elif Qt.__binding__ == "PyQt4": from PyQt4.QtGui import QStyleOptionViewItemV4 +from openpype.client import ( + get_versions, + get_hero_versions, +) +from openpype.pipeline import HeroVersionType +from .models import TreeModel +from . import lib + log = logging.getLogger(__name__) @@ -114,26 +118,24 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): "Version is not integer" ) + project_name = self.dbcon.active_project() # Add all available versions to the editor parent_id = item["version_document"]["parent"] - version_docs = list(self.dbcon.find( - { - "type": "version", - "parent": parent_id - }, - sort=[("name", 1)] + version_docs = list(sorted( + get_versions(project_name, subset_ids=[parent_id]), + key=lambda item: item["name"] )) - hero_version_doc = self.dbcon.find_one( - { - "type": "hero_version", - "parent": parent_id - }, { - "name": 1, - "data.tags": 1, - "version_id": 1 - } + hero_versions = list( + get_hero_versions( + project_name, + subset_ids=[parent_id], + fields=["name", "data.tags", "version_id"] + ) ) + hero_version_doc = None + if hero_versions: + hero_version_doc = hero_versions[0] doc_for_hero_version = None diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 20fea6600b..72ebfcc063 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -6,6 +6,10 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import ( get_default_entity_icon_color, get_objected_colors, @@ -430,9 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = database[project_name].find_one( - {"type": "asset", "name": asset_name}, - {"data.tasks": True} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") @@ -500,10 +503,7 @@ class GroupsConfig: project_name = self.dbcon.Session.get("AVALON_PROJECT") if project_name: # Get pre-defined group name and appearance from project config - project_doc = self.dbcon.find_one( - {"type": "project"}, - projection={"config.groups": True} - ) + project_doc = get_project(project_name, fields=["config.groups"]) if project_doc: group_configs = project_doc["config"].get("groups") or [] diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index eab183d5f3..fcbec318f5 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -47,7 +51,8 @@ class TasksModel(QtGui.QStandardItemModel): # Get the project configured icons from database project_doc = {} if self._context_is_valid(): - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) self._loaded_project_name = self._get_current_project() self._project_doc = project_doc @@ -71,9 +76,9 @@ class TasksModel(QtGui.QStandardItemModel): def set_asset_id(self, asset_id): asset_doc = None if self._context_is_valid(): - asset_doc = self.dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} + project_name = self._get_current_project() + asset_doc = get_asset( + project_name, asset_id=asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 977111b71b..36b9a055d8 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,6 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore +from openpype.client import get_asset from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -384,7 +385,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - self._asset_doc = legacy_io.find_one({"_id": self._asset_id}) + project_name = legacy_io.active_project() + self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + return self._asset_doc def _get_session(self): diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 8f9dd8c6ba..d5b7cef339 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -4,6 +4,11 @@ import logging from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_subsets, + get_versions, + get_representations, +) from openpype.style import ( get_default_entity_icon_color, get_disabled_entity_icon_color, @@ -215,6 +220,7 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._dbcon = dbcon self._anatomy = anatomy + self._file_extensions = extensions self._invalid_context_item = None @@ -234,6 +240,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._asset_id = None self._task_name = None + @property + def project_name(self): + return self._dbcon.Session["AVALON_PROJECT"] + def _set_item_invalid(self, item): item.setFlags(QtCore.Qt.NoItemFlags) item.setData(self._invalid_icon, QtCore.Qt.DecorationRole) @@ -285,15 +295,11 @@ class PublishFilesModel(QtGui.QStandardItemModel): def _get_workfie_representations(self): output = [] # Get subset docs of asset - subset_docs = self._dbcon.find( - { - "type": "subset", - "parent": self._asset_id - }, - { - "_id": True, - "name": True - } + subset_docs = get_subsets( + self.project_name, + asset_ids=[self._asset_id], + fields=["_id", "name"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] @@ -301,17 +307,12 @@ class PublishFilesModel(QtGui.QStandardItemModel): return output # Get version docs of subsets with their families - version_docs = self._dbcon.find( - { - "type": "version", - "parent": {"$in": subset_ids} - }, - { - "_id": True, - "data.families": True, - "parent": True - } + version_docs = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "data.families"] ) + # Filter versions if they contain 'workfile' family filtered_versions = [] for version_doc in version_docs: @@ -327,13 +328,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): # Query representations of filtered versions and add filter for # extension extensions = [ext.replace(".", "") for ext in self._file_extensions] - repre_docs = self._dbcon.find( - { - "type": "representation", - "parent": {"$in": version_ids}, - "context.ext": {"$in": extensions} - } + repre_docs = get_representations( + self.project_name, version_ids, extensions ) + # Filter queried representations by task name if task is set filtered_repre_docs = [] for repre_doc in repre_docs: diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 3e97d6c938..1fbcbfeb22 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -5,6 +5,10 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import ( + get_project, + get_asset, +) from openpype.lib import ( get_last_workfile_with_version, get_workdir_data, @@ -22,29 +26,19 @@ def build_workfile_data(session): """Get the data required for workfile formatting from avalon `session`""" # Set work file data for template formatting + project_name = session["AVALON_PROJECT"] asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] - project_doc = legacy_io.find_one( - {"type": "project"}, - { - "name": True, - "data.code": True, - "config.tasks": True, - } + project_doc = get_project( + project_name, fields=["name", "data.code", "config.tasks"] + ) + asset_doc = get_asset( + project_name, + asset_name=asset_name, + fields=["name", "data.tasks", "data.parents"] ) - asset_doc = legacy_io.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "name": True, - "data.tasks": True, - "data.parents": True - } - ) data = get_workdir_data(project_doc, asset_doc, task_name, host_name) data.update({ "version": 1, diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 02a22af26c..45d8d41d16 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,6 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets +from openpype.client import get_asset from openpype import style from openpype.lib import ( get_workfile_doc, @@ -223,6 +224,10 @@ class Window(QtWidgets.QMainWindow): self._first_show = True self._context_to_set = None + @property + def project_name(self): + return legacy_io.Session["AVALON_PROJECT"] + def showEvent(self, event): super(Window, self).showEvent(event) if self._first_show: @@ -296,7 +301,8 @@ class Window(QtWidgets.QMainWindow): if not workfile_doc: workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() - asset_doc = legacy_io.find_one({"_id": asset_id}) + project_name = legacy_io.active_project() + asset_doc = get_asset(project_name, asset_id=asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -322,14 +328,13 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = legacy_io.find_one( - { - "name": context["asset"], - "type": "asset" - }, - {"_id": 1} - ) or {} - asset_id = asset_doc.get("_id") + asset_doc = get_asset( + self.project_name, context["asset"], fields=["_id"] + ) + + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] # Select the asset self.assets_widget.select_asset(asset_id) self.tasks_widget.set_asset_id(asset_id) From 8bb97b9bbc9f14315aea37de033fa59758c5185a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 6 Jun 2022 11:06:03 +0200 Subject: [PATCH 071/258] Fix - correct project settings selected --- openpype/modules/sync_server/tray/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 049a3f0127..e41910fa4f 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -127,7 +127,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") - elif not sync_settings["enabled"]: + elif not sync_settings[project_name]["enabled"]: icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From b3098a4a1ed2f06c622392870f6fad4f7d122e29 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:10 +0300 Subject: [PATCH 072/258] Fix path bug causing output path to equal input path. --- repos/avalon-core | 1 - 1 file changed, 1 deletion(-) delete mode 160000 repos/avalon-core diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 2fa14cea6f..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From a922b93202cc8ec898981109f260141bb0707ef2 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:30 +0300 Subject: [PATCH 073/258] Fix path bug. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index daf7430a32..f474714780 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -159,9 +159,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(src_path)) + jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( subprocess_command, shell=True, logger=self.log ) From 2f945b9a7b3ce04fa3a37eb3eafac5cdc3efe942 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 11:06:10 +0200 Subject: [PATCH 074/258] Added checkbox to filter only enabled projects Default is true, is not persistent between opening of dialog. --- openpype/modules/sync_server/tray/app.py | 36 +++++++++++++++----- openpype/modules/sync_server/tray/widgets.py | 3 ++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index fc8558bdbc..4aa3d430fa 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -46,6 +46,14 @@ class SyncServerWindow(QtWidgets.QDialog): left_column_layout.addWidget(self.pause_btn) + checkbox = QtWidgets.QCheckBox("Show only enabled", self) + checkbox.setStyleSheet("QCheckBox{spacing: 5px;" + "padding:5px 5px 5px 5px;}") + checkbox.setChecked(True) + self.show_only_enabled_chk = checkbox + + left_column_layout.addWidget(self.show_only_enabled_chk) + repres = SyncRepresentationSummaryWidget( sync_server, project=self.projects.current_project, @@ -86,8 +94,23 @@ class SyncServerWindow(QtWidgets.QDialog): repres.message_generated.connect(self._update_message) self.projects.message_generated.connect(self._update_message) + self.show_only_enabled_chk.stateChanged.connect( + self._on_enabled_change + ) + self.representationWidget = repres + def showEvent(self, event): + self.representationWidget.model.set_project( + self.projects.current_project) + self.projects.refresh() + self._set_running(True) + super().showEvent(event) + + def closeEvent(self, event): + self._set_running(False) + super().closeEvent(event) + def _on_project_change(self): if self.projects.current_project is None: return @@ -103,16 +126,11 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.refresh() return - def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + def _on_enabled_change(self): + """Called when enabled projects only checkbox is toggled.""" + self.projects.show_only_enabled = \ + self.show_only_enabled_chk.isChecked() self.projects.refresh() - self._set_running(True) - super().showEvent(event) - - def closeEvent(self, event): - self._set_running(False) - super().closeEvent(event) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e41910fa4f..88ed2c1d37 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -47,6 +47,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): message_generated = QtCore.Signal(str) refresh_msec = 10000 + show_only_enabled = True def __init__(self, sync_server, parent): super(SyncProjectListWidget, self).__init__(parent) @@ -128,6 +129,8 @@ class SyncProjectListWidget(QtWidgets.QWidget): self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") elif not sync_settings[project_name]["enabled"]: + if self.show_only_enabled: + continue icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From 9babdb7832c9cc6644e0dc4ff8f3d024b068fd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 14:18:48 +0200 Subject: [PATCH 075/258] :recycle: limit the scope of file attribute skipping filter --- openpype/hosts/maya/plugins/publish/collect_look.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 9b6d1d999c..60af5238d0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,8 +601,10 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) - if not source: - self.log.info("source is empty, skipping...") + # renderman allows nodes to have filename attribute empty while + # you can have another incoming connection from different node. + if not source and cmds.nodeType(node).lower().startswith("pxr"): + self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the From 71cabc76c4386bff5c8d89ab0afcc6d5d15c61ab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 15:13:08 +0200 Subject: [PATCH 076/258] Handle when no projects are shown or selected --- openpype/modules/sync_server/tray/app.py | 1 + openpype/modules/sync_server/tray/models.py | 33 +++++++++++++------- openpype/modules/sync_server/tray/widgets.py | 6 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index 4aa3d430fa..dee3bf0ecc 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -131,6 +131,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() + self.representationWidget.model.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 6b309312a2..c49edeafb9 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -52,7 +52,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): All queries should go through this (because of collection). """ - return self.sync_server.connection.database[self.project] + if self.project: + return self.sync_server.connection.database[self.project] @property def project(self): @@ -150,6 +151,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): @property def can_edit(self): """Returns true if some site is user local site, eg. could edit""" + if not self.project: + return False + return get_local_site_id() in (self.active_site, self.remote_site) def get_column(self, index): @@ -190,7 +194,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): actually queried (scrolled a couple of times to list more than single page of records) """ - if self.is_editing or not self.is_running: + if self.is_editing or not self.is_running or not self.project: return self.refresh_started.emit() self.beginResetModel() @@ -232,6 +236,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): more records in DB than loaded. """ log.debug("fetchMore") + if not self.dbcon: + return + items_to_fetch = min(self._total_records - self._rec_loaded, self.PAGE_SIZE) self.query = self.get_query(self._rec_loaded) @@ -286,9 +293,10 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): # replace('False', 'false').\ # replace('True', 'true').replace('None', 'null')) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) + if self.dbcon: + representations = self.dbcon.aggregate(pipeline=self.query, + allowDiskUse=True) + self.refresh(representations) def set_word_filter(self, word_filter): """ @@ -380,6 +388,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._project = project # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): + self._data = {} return self.active_site = self.sync_server.get_active_site(self.project) @@ -508,21 +517,23 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._word_filter = None - if not self._project or self._project == lib.DUMMY_PROJECT: - return - self.sync_server = sync_server # TODO think about admin mode + self.sort_criteria = self.DEFAULT_SORT + + self.timer = QtCore.QTimer() + if not self._project or self._project == lib.DUMMY_PROJECT: + self.active_site = sync_server.DEFAULT_SITE + self.remote_site = sync_server.DEFAULT_SITE + return + # this is for regular user, always only single local and single remote self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) - self.sort_criteria = self.DEFAULT_SORT - self.query = self.get_query() self.default_query = list(self.get_query()) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 88ed2c1d37..89ab12ea4d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -144,12 +144,12 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.current_project == project_name: selected_item = item + if model.item(0) is None: + return + if selected_item: selected_index = model.indexFromItem(selected_item) - if len(self.sync_server.sync_project_settings.keys()) == 0: - model.appendRow(QtGui.QStandardItem(lib.DUMMY_PROJECT)) - if not self.current_project: self.current_project = model.item(0).data(QtCore.Qt.DisplayRole) From 9829410932e18f85f4e899a2af045e65aa1d12ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 15:16:39 +0200 Subject: [PATCH 077/258] implemented get_representation_parents for single representation --- openpype/client/__init__.py | 2 ++ openpype/client/entities.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 861f828e67..e4c01fc0cc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -22,6 +22,7 @@ from .entities import ( get_representation, get_representation_by_name, get_representations, + get_representation_parents, get_representations_parents, get_thumbnail, @@ -52,6 +53,7 @@ __all__ = ( "get_representation", "get_representation_by_name", "get_representations", + "get_representation_parents", "get_representations_parents", "get_thubmnail", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 4b52f8cf2d..e9b820dd1a 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -641,6 +641,15 @@ def get_representations_parents(project_name, representations): return output +def get_representation_parents(project_name, representation): + if not representation: + return None + + repre_id = representation["_id"] + parents_by_repre_id = get_representations(project_name, [representation]) + return parents_by_repre_id.get(repre_id) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): if not src_type or not src_id: return None From f760c9d767c46a0ee433e83890cc15b4565f412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 15:28:11 +0200 Subject: [PATCH 078/258] :recycle: more explicit renderman check --- openpype/hosts/maya/plugins/publish/collect_look.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 60af5238d0..4a34cfdea2 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -603,7 +603,14 @@ class CollectLook(pyblish.api.InstancePlugin): # renderman allows nodes to have filename attribute empty while # you can have another incoming connection from different node. - if not source and cmds.nodeType(node).lower().startswith("pxr"): + pxr_nodes = set() + if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): + pxr_nodes = set( + cmds.pluginInfo("RenderMan_for_Maya", + query=True, + dependNode=True) + ) + if not source and cmds.nodeType(node) in pxr_nodes: self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray From 0d38c76f5402cd99e5a70385667ff1a5f0885eb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:28:53 +0200 Subject: [PATCH 079/258] separated functions to query asset by name and id --- openpype/client/__init__.py | 8 ++-- openpype/client/entities.py | 43 ++++++++++++++----- openpype/tools/creator/window.py | 6 +-- openpype/tools/mayalookassigner/commands.py | 4 +- .../tools/publisher/widgets/create_dialog.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 32 +++++++------- openpype/tools/sceneinventory/view.py | 4 +- openpype/tools/standalonepublish/app.py | 6 +-- .../standalonepublish/widgets/widget_asset.py | 10 ++--- .../widgets/widget_family.py | 14 +++--- openpype/tools/texture_copy/app.py | 4 +- openpype/tools/utils/lib.py | 6 +-- openpype/tools/utils/tasks_widget.py | 6 +-- openpype/tools/workfiles/files_widget.py | 4 +- openpype/tools/workfiles/save_as_dialog.py | 6 +-- openpype/tools/workfiles/window.py | 6 +-- 17 files changed, 94 insertions(+), 73 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e4c01fc0cc..2ba2a3693e 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -2,7 +2,8 @@ from .entities import ( get_projects, get_project, - get_asset, + get_asset_by_id, + get_asset_by_name, get_assets, get_asset_ids_with_subsets, @@ -33,7 +34,8 @@ __all__ = ( "get_projects", "get_project", - "get_asset", + "get_asset_by_id", + "get_asset_by_name", "get_assets", "get_asset_ids_with_subsets", @@ -56,6 +58,6 @@ __all__ = ( "get_representation_parents", "get_representations_parents", - "get_thubmnail", + "get_thumbnail", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index e9b820dd1a..97553f30fe 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -90,23 +90,44 @@ def get_project(project_name, active=True, inactive=False, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) -def get_asset(project_name, asset_name=None, asset_id=None, fields=None): - query_filter = {"type": "asset"} - has_filter = False - if asset_name: - has_filter = True - query_filter["name"] = asset_name +def get_asset_by_id(project_name, asset_id, fields=None): + """Receive asset data by it's id. - if asset_id: - has_filter = True - query_filter["_id"] = _convert_id(asset_id) + Args: + project_name (str): Name of project where to look for subset. + asset_id (str|ObjectId): Asset's id. - # Avoid random asset quqery - if not has_filter: + Returns: + dict: Asset entity data. + None: Asset was not found by id. + """ + + asset_id = _convert_id(asset_id) + if not asset_id: return None + query_filter = {"type": "asset", "_id": asset_id} conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + +def get_asset_by_name(project_name, asset_name, fields=None): + """Receive asset data by it's name. + + Args: + project_name (str): Name of project where to look for subset. + asset_name (str): Asset's name. + + Returns: + dict: Asset entity data. + None: Asset was not found by name. + """ + + if not asset_name: + return None + + query_filter = {"type": "asset", "name": asset_name} + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index a85f47a060..a3937d6a40 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,7 +4,7 @@ import re from Qt import QtWidgets, QtCore -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -220,8 +220,8 @@ class CreatorWindow(QtWidgets.QDialog): asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index a4fc1fab70..2e7a51efde 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -4,7 +4,7 @@ import os import maya.cmds as cmds -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.pipeline import ( legacy_io, remove_container, @@ -161,7 +161,7 @@ def create_items_from_nodes(nodes): project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = get_asset(project_name, asset_id=_id, fields=["name"]) + asset = get_asset_by_id(project_name, _id, fields=["name"]) # Skip if asset id is not found if not asset: diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d579831b21..53bbef8b75 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -10,7 +10,7 @@ except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -650,7 +650,7 @@ class CreateDialog(QtWidgets.QDialog): return project_name = self.dbcon.active_project() - asset_doc = get_asset(project_name, asset_name=asset_name) + asset_doc = get_asset_by_name(project_name, asset_name) self._asset_doc = asset_doc if asset_doc: diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 9cf69ed650..6d813af45b 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -7,7 +7,7 @@ from Qt import QtCore, QtGui import qtawesome from openpype.client import ( - get_asset, + get_asset_by_id, get_subset, get_version, get_last_version_for_subset, @@ -342,7 +342,7 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - asset = get_asset(project_name, asset_id=subset["parent"]) + asset = get_asset_by_id(project_name, subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b940c66a56..6d6294af6f 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -5,7 +5,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_asset, + get_asset_by_name, get_assets, get_subset, get_subsets, @@ -484,9 +484,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.active_project(), - asset_name=selected_asset, + selected_asset, fields=["_id"] ) if not asset_doc: @@ -768,8 +768,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) asset_ids = [asset_doc["_id"]] else: @@ -832,8 +832,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -858,8 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -995,8 +995,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] project_name = self.active_project() - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = get_subsets( project_name, asset_ids=[asset_doc["_id"]], fields=["name"] @@ -1043,8 +1043,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [ ] project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -1077,8 +1077,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = list(get_subsets( project_name, @@ -1189,7 +1189,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() if selected_asset: - asset_doc = get_asset(project_name, asset_name=selected_asset) + asset_doc = get_asset_by_name(project_name, selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 6a95ccb57b..04e0f67a15 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -657,11 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - representation_id = ObjectId(active["representation"]) - repre_doc = get_representation( project_name, - representation_id=representation_id, + representation_id=active["representation"], fields=["parent"] ) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 4831db038c..3ceeb3ad48 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,7 +6,7 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset +from openpype.client import get_asset_by_id from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget @@ -144,8 +144,8 @@ class Window(QtWidgets.QDialog): if len(selected) == 1: self.valid_parent = True project_name = self.db.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["name"] + asset = get_asset_by_id( + project_name, selected[0], fields=["name"] ) self.widget_family.change_asset(asset['name']) else: diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 0b5802ed9e..73114f7960 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -4,7 +4,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.tools.utils import PlaceholderLineEdit @@ -251,9 +251,9 @@ class AssetWidget(QtWidgets.QWidget): return output project_name = self.dbcon.active_project() - parent = get_asset( + parent = get_asset_by_id( project_name, - asset_id=parent_asset_id, + parent_asset_id, fields=["name", "data.visualParent"] ) output.append(parent['name']) @@ -362,8 +362,8 @@ class AssetWidget(QtWidgets.QWidget): selected = self.get_selected_assets() if len(selected) == 1: project_name = self.dbcon.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["data.tasks"] + asset = get_asset_by_id( + project_name, selected[0], fields=["data.tasks"] ) if asset: tasks = asset.get('data', {}).get('tasks', []) diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index ed9f405f38..fa157d37f1 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -3,7 +3,7 @@ import re from Qt import QtWidgets, QtCore from openpype.client import ( - get_asset, + get_asset_by_name, get_subset, get_subsets, get_last_version_for_subset, @@ -188,8 +188,8 @@ class FamilyWidget(QtWidgets.QWidget): if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin and family @@ -205,8 +205,8 @@ class FamilyWidget(QtWidgets.QWidget): # Get the asset from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) @@ -310,8 +310,8 @@ class FamilyWidget(QtWidgets.QWidget): asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) if asset_doc: diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index 8703f075d3..746a72b3ec 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,7 +4,7 @@ import click import speedcopy -from openpype.client import get_project, get_asset +from openpype.client import get_project, get_asset_by_name from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -94,7 +94,7 @@ class TextureCopy: t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = get_asset(project_name, asset_name=asset_name) + asset = get_asset_by_name(project_name, asset_name) if not asset: t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 72ebfcc063..ea1362945f 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.style import ( get_default_entity_icon_color, @@ -434,8 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["data.tasks"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index fcbec318f5..0353f3dd2f 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -3,7 +3,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -77,8 +77,8 @@ class TasksModel(QtGui.QStandardItemModel): asset_doc = None if self._context_is_valid(): project_name = self._get_current_project() - asset_doc = get_asset( - project_name, asset_id=asset_id, fields=["data.tasks"] + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 36b9a055d8..68fe8301c9 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,7 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -386,7 +386,7 @@ class FilesWidget(QtWidgets.QWidget): if self._asset_doc is None: project_name = legacy_io.active_project() - self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + self._asset_doc = get_asset_by_id(project_name, self._asset_id) return self._asset_doc diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 1fbcbfeb22..b62fd2c889 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -7,7 +7,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.lib import ( get_last_workfile_with_version, @@ -33,9 +33,9 @@ def build_workfile_data(session): project_doc = get_project( project_name, fields=["name", "data.code", "config.tasks"] ) - asset_doc = get_asset( + asset_doc = get_asset_by_name( project_name, - asset_name=asset_name, + asset_name, fields=["name", "data.tasks", "data.parents"] ) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 45d8d41d16..9f4cea2f8a 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,7 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets -from openpype.client import get_asset +from openpype.client import get_asset_by_id, get_asset_by_name from openpype import style from openpype.lib import ( get_workfile_doc, @@ -302,7 +302,7 @@ class Window(QtWidgets.QMainWindow): workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() project_name = legacy_io.active_project() - asset_doc = get_asset(project_name, asset_id=asset_id) + asset_doc = get_asset_by_id(project_name, asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -328,7 +328,7 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.project_name, context["asset"], fields=["_id"] ) From 20ecefa4aae7918fb6a0dbdd5d3de141249eb69d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:43:07 +0200 Subject: [PATCH 080/258] added docstrings for asset queries --- openpype/client/entities.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 97553f30fe..16dc3d76a9 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -94,8 +94,10 @@ def get_asset_by_id(project_name, asset_id, fields=None): """Receive asset data by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_id (str|ObjectId): Asset's id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -115,8 +117,10 @@ def get_asset_by_name(project_name, asset_name, fields=None): """Receive asset data by it's name. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_name (str): Asset's name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -134,6 +138,25 @@ def get_asset_by_name(project_name, asset_name, fields=None): def get_assets( project_name, asset_ids=None, asset_names=None, archived=False, fields=None ): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + asset_types = ["asset"] if archived: asset_types.append("archived_asset") @@ -160,6 +183,16 @@ def get_assets( def get_asset_ids_with_subsets(project_name, asset_ids=None): + """Find out which assets have existing subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Look only for entered asset ids. + + Returns: + List[ObjectId]: Asset ids that have existing subsets. + """ + subset_query = { "type": "subset" } From 9456dac8b92862120f580bfbd59327b83a78fd4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:44:26 +0200 Subject: [PATCH 081/258] separated function get_subset into 2 separated functions --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 73 ++++++++++--------- openpype/tools/loader/widgets.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 26 +++---- .../widgets/widget_family.py | 8 +- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ba2a3693e..34257cf3dc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -7,7 +7,8 @@ from .entities import ( get_assets, get_asset_ids_with_subsets, - get_subset, + get_subset_by_id, + get_subset_by_name, get_subsets, get_subset_families, @@ -39,7 +40,8 @@ __all__ = ( "get_assets", "get_asset_ids_with_subsets", - "get_subset", + "get_subset_by_id", + "get_subset_by_name", "get_subsets", "get_subset_families", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 16dc3d76a9..07f6bb2e65 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -223,29 +223,13 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): return asset_ids_with_subsets -def get_subset( - project_name, - subset_id=None, - subset_name=None, - asset_id=None, - fields=None -): - """Single subset document by subset id or name and parent id. - - When subset id is defined it is not needed to add any other arguments but - subset name filter must be always combined with asset id (or subset id). - - Question: - This could be split into more functions? +def get_subset_by_id(project_name, subset_id, fields=None): + """Single subset document by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. subset_id (ObjectId): Id of subset which should be found. - subset_name (str): Name of asset. Must be combined with 'asset_id' or - 'subset_id' arguments otherwise result is 'None'. - asset_id (ObjectId): Id of parent asset. Must be combined with - 'subset_name' or 'subset_id'. - fields (list): Fields that should be returned. All fields are + fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -253,23 +237,42 @@ def get_subset( Dict: Subset document which can be reduced to specified 'fields'. """ - query_filters = {"type": "subset"} - has_valid_filters = False - if subset_id is not None: - query_filters["_id"] = _convert_id(asset_id) - has_valid_filters = True - - if subset_name is not None: - if asset_id is not None: - has_valid_filters = True - query_filters["name"] = subset_name - - if asset_id is not None: - query_filters["parent"] = _convert_id(asset_id) - - if not has_valid_filters: + subset_id = _convert_id(subset_id) + if not subset_id: return None + query_filters = {"type": "subset", "_id": subset_id} + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subset_by_name(project_name, subset_name, asset_id, fields=None): + """Single subset document by subset name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Id of parent asset. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + if not subset_name: + return None + + asset_id = _convert_id(asset_id) + if not asset_id: + return None + + query_filters = { + "type": "subset", + "name": subset_name, + "parent": asset_id + } conn = _get_project_connection(project_name) return conn.find_one(query_filters, _prepare_fields(fields)) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 6c7acc593d..4a9a911f93 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -9,7 +9,7 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.client import ( get_subset_families, - get_subset, + get_subset_by_id, get_subsets, get_version, get_versions, @@ -688,7 +688,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): _version_doc["name"] ) - subset = get_subset(project_name, subset_id=version_doc["parent"]) + subset = get_subset_by_id(project_name, version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 6d813af45b..b36f7f4cea 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, - get_subset, + get_subset_by_id, get_version, get_last_version_for_subset, get_representation, @@ -336,7 +336,7 @@ class InventoryModel(TreeModel): version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = get_subset(project_name, subset_id=version["parent"]) + subset = get_subset_by_id(project_name, version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 6d6294af6f..92ef2b3553 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -7,7 +7,7 @@ from bson.objectid import ObjectId from openpype.client import ( get_asset_by_name, get_assets, - get_subset, + get_subset_by_name, get_subsets, get_versions, get_hero_versions, @@ -537,10 +537,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): self, asset_doc, selected_subset, selected_repre ): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=selected_subset, - asset_id=asset_doc["_id"], + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -560,10 +560,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) if not subset_doc: @@ -835,10 +835,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -1046,10 +1046,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) subset_id = subset_doc["_id"] diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index fa157d37f1..2f00cfe7bb 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -4,7 +4,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_asset_by_name, - get_subset, + get_subset_by_name, get_subsets, get_last_version_for_subset, ) @@ -315,10 +315,10 @@ class FamilyWidget(QtWidgets.QWidget): ) if asset_doc: - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=subset_name, - asset_id=asset_doc['_id'], + subset_name, + asset_doc['_id'], fields=["_id"] ) From 0d5cc529d502cdf2a421be4f7db27dcbd54eb9ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:18:21 +0200 Subject: [PATCH 082/258] renamed 'get_version' to 'get_version_by_id' --- openpype/client/__init__.py | 4 +- openpype/client/entities.py | 107 ++++++++++++++++++++++--- openpype/tools/loader/widgets.py | 8 +- openpype/tools/sceneinventory/model.py | 10 +-- openpype/tools/sceneinventory/view.py | 6 +- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 34257cf3dc..e8e3a81d5d 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -12,8 +12,8 @@ from .entities import ( get_subsets, get_subset_families, + get_version_by_id, get_version_by_name, - get_version, get_versions, get_last_versions, get_last_version_for_subset, @@ -45,8 +45,8 @@ __all__ = ( "get_subsets", "get_subset_families", + "get_version_by_id", "get_version_by_name", - "get_version", "get_versions", "get_last_versions", "get_last_version_for_subset", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 07f6bb2e65..1a45be6e93 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -224,7 +224,7 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): def get_subset_by_id(project_name, subset_id, fields=None): - """Single subset document by it's id. + """Single subset entity data by it's id. Args: project_name (str): Name of project where to look for queried entities. @@ -247,7 +247,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): def get_subset_by_name(project_name, subset_name, asset_id, fields=None): - """Single subset document by subset name and it's version id. + """Single subset entity data by it's name and it's version id. Args: project_name (str): Name of project where to look for queried entities. @@ -279,12 +279,31 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): def get_subsets( project_name, - asset_ids=None, subset_ids=None, subset_names=None, + asset_ids=None, archived=False, fields=None ): + """Subset entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + Filter ignored if 'None' is passed. + subset_names (list[str]): Subset names that should be queried. + Filter ignored if 'None' is passed. + asset_ids (list[str, ObjectId]): Asset ids under which should look for + the subsets. Filter ignored if 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching subsets. + """ + subset_types = ["subset"] if archived: subset_types.append("archived_subset") @@ -316,6 +335,17 @@ def get_subsets( def get_subset_families(project_name, subset_ids=None): + """Set of main families of subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + All subsets from project are used if 'None' is passed. + + Returns: + set[str]: Main families of matching subsets. + """ + subset_filter = { "type": "subset" } @@ -340,23 +370,56 @@ def get_subset_families(project_name, subset_ids=None): return set() -def get_version_by_name(project_name, subset_id, version, fields=None): - conn = _get_project_connection(project_name) +def get_version_by_id(project_name, version_id, fields=None): + """Single version entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + query_filter = { - "type": "version", - "parent": _convert_id(subset_id), - "name": version + "type": {"$in": ["version", "hero_version"]}, + "_id": version_id } + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) -def get_version(project_name, version_id, fields=None): - if not version_id: +def get_version_by_name(project_name, version, subset_id, fields=None): + """Single version entity data by it's name and subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + version (int): name of version entity (it's version). + subset_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None + conn = _get_project_connection(project_name) query_filter = { - "type": {"$in": ["version", "hero_version"]}, - "_id": _convert_id(version_id) + "type": "version", + "parent": subset_id, + "name": version } return conn.find_one(query_filter, _prepare_fields(fields)) @@ -402,11 +465,29 @@ def _get_versions( def get_versions( project_name, - subset_ids=None, version_ids=None, + subset_ids=None, hero=False, fields=None ): + """Version entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + version_ids (list[str, ObjectId]): Version ids that will be queried. + Filter ignored if 'None' is passed. + subset_ids (list[str]): Subset ids that will be queried. + Filter ignored if 'None' is passed. + hero (bool): Look also for hero versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching versions. + """ + return _get_versions( project_name, subset_ids, diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 4a9a911f93..921708922e 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_families, get_subset_by_id, get_subsets, - get_version, + get_version_by_id, get_versions, get_representations, get_thumbnail_id_from_source, @@ -676,12 +676,12 @@ class VersionTextEdit(QtWidgets.QTextEdit): project_name = self.dbcon.active_project() if not version_doc: - version_doc = get_version(project_name, version_id=version_id) + version_doc = get_version_by_id(project_name, version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = get_version( - project_name, version_id=version_doc["version_id"] + _version_doc = get_version_by_id( + project_name, version_doc["version_id"] ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index b36f7f4cea..a5d856fe72 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -9,7 +9,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, get_subset_by_id, - get_version, + get_version_by_id, get_last_version_for_subset, get_representation, ) @@ -321,8 +321,8 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - version = get_version( - project_name, version_id=representation["parent"] + version = get_version_by_id( + project_name, representation["parent"] ) if not version: not_found["version"].append(group_items) @@ -330,8 +330,8 @@ class InventoryModel(TreeModel): continue elif version["type"] == "hero_version": - _version = get_version( - project_name, version_id=version["version_id"] + _version = get_version_by_id( + project_name, version["version_id"] ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 04e0f67a15..d1ff91535f 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -7,7 +7,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_version, + get_version_by_id, get_versions, get_hero_versions, get_representation, @@ -663,9 +663,9 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - repre_version_doc = get_version( + repre_version_doc = get_version_by_id( project_name, - version_id=repre_doc["parent"], + repre_doc["parent"], fields=["parent"] ) From 0f95a66359c693382af236e4d695764ecbfb7f14 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:43:44 +0200 Subject: [PATCH 083/258] renamed get_version_links to get_output_link_versions --- openpype/client/__init__.py | 4 ++-- openpype/client/entities.py | 26 ++++++++++++++++++++++++-- openpype/tools/assetlinks/widgets.py | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e8e3a81d5d..7230a7c153 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,7 @@ from .entities import ( get_last_version_for_subset, get_hero_version, get_hero_versions, - get_version_links, + get_output_link_versions, get_representation, get_representation_by_name, @@ -52,7 +52,7 @@ __all__ = ( "get_last_version_for_subset", "get_hero_version", "get_hero_versions", - "get_version_links", + "get_output_link_versions", "get_representation", "get_representation_by_name", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1a45be6e93..0eb0367452 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -544,12 +544,34 @@ def get_hero_versions( ) -def get_version_links(project_name, version_id, fields=None): +def get_output_link_versions(project_name, version_id, fields=None): + """Versions where passed version was used as input. + + Question: + Not 100% sure about the usage of the function so the name and docstring + maybe does not match what it does? + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Version id which can be used as input link + for other versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable cursor yielding versions that are used as input + links for passed version. + """ + + version_id = _convert_id(version_id) + if not version_id: + return [] + conn = _get_project_connection(project_name) # Does make sense to look for hero versions? query_filter = { "type": "version", - "data.inputLinks.input": _convert_id(version_id) + "data.inputLinks.input": version_id } return conn.find(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 5ce2a835ef..3078585ed1 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -3,7 +3,7 @@ from openpype.client import ( get_versions, get_subsets, get_assets, - get_version_links, + get_output_link_versions, ) from Qt import QtWidgets @@ -112,7 +112,7 @@ class SimpleLinkView(QtWidgets.QWidget): )) def _fill_outputs(self, version_doc): - version_docs = list(get_version_links( + version_docs = list(get_output_link_versions( self.project_name, version_doc["_id"], fields=["name", "parent"] From 675da63f8ccb862797c298ba9acd6a57b7f7e1e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:04:04 +0200 Subject: [PATCH 084/258] split 'get_hero_version' to 'get_hero_version_by_id' and 'get_hero_version_by_subset_id' --- openpype/client/__init__.py | 10 +++-- openpype/client/entities.py | 79 +++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 7230a7c153..4f8b948c93 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -15,10 +15,11 @@ from .entities import ( get_version_by_id, get_version_by_name, get_versions, + get_hero_version_by_id, + get_hero_version_by_subset_id, + get_hero_versions, get_last_versions, get_last_version_for_subset, - get_hero_version, - get_hero_versions, get_output_link_versions, get_representation, @@ -48,10 +49,11 @@ __all__ = ( "get_version_by_id", "get_version_by_name", "get_versions", + "get_hero_version_by_id", + "get_hero_version_by_subset_id", + "get_hero_versions", "get_last_versions", "get_last_version_for_subset", - "get_hero_version", - "get_hero_versions", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 0eb0367452..bf033e7c81 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -498,27 +498,57 @@ def get_versions( ) -def get_hero_version( - project_name, - subset_id=None, - version_id=None, - fields=None -): - if not subset_id and not version_id: +def get_hero_version_by_subset_id(project_name, subset_id, fields=None): + """Hero version by subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Subset id under which is hero version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version for passed subset id does not exists. + Dict: Hero version entity data. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_ids = None - if subset_id is not None: - subset_ids = [subset_id] - - version_ids = None - if version_id is not None: - version_ids = [version_id] - versions = list(_get_versions( project_name, - subset_ids=subset_ids, - version_ids=version_ids, + subset_ids=[subset_id], + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_version_by_id(project_name, version_id, fields=None): + """Hero version by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Hero version id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version with passed id was not found. + Dict: Hero version entity data. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + + versions = list(_get_versions( + project_name, + version_ids=[version_id], standard=False, hero=True, fields=fields @@ -534,6 +564,21 @@ def get_hero_versions( version_ids=None, fields=None ): + """Hero version entities data from one project filtered by entered filters. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids for which should look for + hero versions. Filter ignored if 'None' is passed. + version_ids (list[str|ObjectId]): Hero version ids. Filter ignored if + 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable yielding hero versions matching passed filters. + """ + return _get_versions( project_name, subset_ids, From ba6ef6d2ae035361d0a7282983555990c322cc7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:23:44 +0200 Subject: [PATCH 085/258] split 'get_last_version_for_subset' into 'get_last_version_by_subset_id' and 'get_last_version_by_subset_name' --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 81 ++++++++++++------- openpype/tools/mayalookassigner/app.py | 6 +- .../tools/mayalookassigner/vray_proxies.py | 4 +- openpype/tools/sceneinventory/model.py | 6 +- .../widgets/widget_family.py | 6 +- 6 files changed, 65 insertions(+), 44 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 4f8b948c93..2ef32d6a83 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,8 @@ from .entities import ( get_hero_version_by_subset_id, get_hero_versions, get_last_versions, - get_last_version_for_subset, + get_last_version_by_subset_id, + get_last_version_by_subset_name, get_output_link_versions, get_representation, @@ -53,7 +54,8 @@ __all__ = ( "get_hero_version_by_subset_id", "get_hero_versions", "get_last_versions", - "get_last_version_for_subset", + "get_last_version_by_subset_id", + "get_last_version_by_subset_name", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index bf033e7c81..232d9aebcc 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -228,7 +228,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (ObjectId): Id of subset which should be found. + subset_id (str|ObjectId): Id of subset which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -375,7 +375,7 @@ def get_version_by_id(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (ObjectId): Id of version which should be found. + version_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -402,7 +402,7 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. version (int): name of version entity (it's version). - subset_id (ObjectId): Id of version which should be found. + subset_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -622,13 +622,13 @@ def get_output_link_versions(project_name, version_id, fields=None): def get_last_versions(project_name, subset_ids, fields=None): - """Retrieve all latest versions for entered subset_ids. + """Latest versions for entered subset_ids. Args: subset_ids (list): List of subset ids. Returns: - dict: Key is subset id and value is last version name. + dict[ObjectId, int]: Key is subset id and value is last version name. """ subset_ids = _convert_ids(subset_ids) @@ -665,35 +665,60 @@ def get_last_versions(project_name, subset_ids, fields=None): } -def get_last_version_for_subset( - project_name, subset_id=None, subset_name=None, asset_id=None, fields=None -): - subset_doc = get_subset( - project_name, - subset_id=subset_id, - subset_name=subset_name, - asset_id=asset_id, - fields=["_id"] - ) - if not subset_doc: +def get_last_version_by_subset_id(project_name, subset_id, fields=None): + """Last version for passed subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_id = subset_doc["_id"] + last_versions = get_last_versions( project_name, subset_ids=[subset_id], fields=fields ) return last_versions.get(subset_id) -def get_representation( - project_name, - representation_id=None, - representation_name=None, - version_id=None, - fields=None +def get_last_version_by_subset_name( + project_name, subset_name, asset_id, fields=None ): + """Last version for passed subset name under asset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Asset id which is parnt of passed subset name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_doc = get_subset_by_name( + project_name, subset_name, asset_id, fields=["_id"] + ) + if not subset_doc: + return None + return get_last_version_by_subset_id( + project_name, subset_doc["_id"], fields=fields + ) + + +def get_representation(project_name, representation_id, fields=None): if not representation_id: - if not representation_name or not version_id: - return None + return None repre_types = ["representation", "archived_representations"] query_filter = { @@ -702,12 +727,6 @@ def get_representation( if representation_id is not None: query_filter["_id"] = _convert_id(representation_id) - if representation_name is not None: - query_filter["name"] = representation_name - - if version_id is not None: - query_filter["parent"] = version_id - conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 427edf8245..5665acea42 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,7 +4,7 @@ import logging from Qt import QtWidgets, QtCore -from openpype.client import get_last_version_for_subset +from openpype.client import get_last_version_by_subset_id from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -230,8 +230,8 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): continue # Get the latest version of this asset's look subset - version = get_last_version_for_subset( - project_name, subset_id=assign_look["_id"], fields=["_id"] + version = get_last_version_by_subset_id( + project_name, assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index b2ba21f944..889396e555 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -12,7 +12,7 @@ from maya import cmds from openpype.client import ( get_representation_by_name, - get_last_version_for_subset, + get_last_version_by_subset_name, ) from openpype.pipeline import ( legacy_io, @@ -251,7 +251,7 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - version = get_last_version_for_subset( + version = get_last_version_by_subset_name( project_name, subset_name=subset, asset_id=asset_id, diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index a5d856fe72..8c49933e80 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -10,7 +10,7 @@ from openpype.client import ( get_asset_by_id, get_subset_by_id, get_version_by_id, - get_last_version_for_subset, + get_last_version_by_subset_id, get_representation, ) from openpype.pipeline import ( @@ -403,8 +403,8 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = get_last_version_for_subset( - project_name, subset_id=version["parent"] + highest_version = get_last_version_by_subset_id( + project_name, version["parent"] ) # create the group header diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 2f00cfe7bb..1736be84ab 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -6,7 +6,7 @@ from openpype.client import ( get_asset_by_name, get_subset_by_name, get_subsets, - get_last_version_for_subset, + get_last_version_by_subset_id, ) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator @@ -323,9 +323,9 @@ class FamilyWidget(QtWidgets.QWidget): ) if subset_doc: - last_version = get_last_version_for_subset( + last_version = get_last_version_by_subset_id( project_name, - subset_id=subset_doc["_id"], + subset_doc["_id"], fields=["name"] ) if last_version: From cde47eefa8201fb6fc7b922427436ec2f3bd0202 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:27:54 +0200 Subject: [PATCH 086/258] renamed 'get_representation' to 'get_representation_by_id' --- openpype/client/__init__.py | 4 +-- openpype/client/entities.py | 35 +++++++++++++++++++++++--- openpype/tools/sceneinventory/model.py | 6 ++--- openpype/tools/sceneinventory/view.py | 6 ++--- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ef32d6a83..2fa7730839 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -23,7 +23,7 @@ from .entities import ( get_last_version_by_subset_name, get_output_link_versions, - get_representation, + get_representation_by_id, get_representation_by_name, get_representations, get_representation_parents, @@ -58,7 +58,7 @@ __all__ = ( "get_last_version_by_subset_name", "get_output_link_versions", - "get_representation", + "get_representation_by_id", "get_representation_by_name", "get_representations", "get_representation_parents", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 232d9aebcc..7abb25e380 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -716,7 +716,21 @@ def get_last_version_by_subset_name( ) -def get_representation(project_name, representation_id, fields=None): +def get_representation_by_id(project_name, representation_id, fields=None): + """Representation entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_id (str|ObjectId): Representation id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + if not representation_id: return None @@ -735,17 +749,32 @@ def get_representation(project_name, representation_id, fields=None): def get_representation_by_name( project_name, representation_name, version_id, fields=None ): + """Representation entity data by it's name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_name (str): Representation name. + version_id (str|ObjectId): Id of parent version entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + + version_id = _convert_id(version_id) if not version_id or not representation_name: return None repre_types = ["representation", "archived_representations"] query_filter = { "type": {"$in": repre_types}, "name": representation_name, - "parent": _convert_id(version_id) + "parent": version_id } conn = _get_project_connection(project_name) - return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8c49933e80..117bdfcba1 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_by_id, get_version_by_id, get_last_version_by_subset_id, - get_representation, + get_representation_by_id, ) from openpype.pipeline import ( legacy_io, @@ -313,8 +313,8 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = get_representation( - project_name, representation_id=repre_id + representation = get_representation_by_id( + project_name, repre_id ) if not representation: not_found["representation"].append(group_items) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index d1ff91535f..8164c48a5d 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -10,7 +10,7 @@ from openpype.client import ( get_version_by_id, get_versions, get_hero_versions, - get_representation, + get_representation_by_id, get_representations, ) from openpype import style @@ -657,9 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - repre_doc = get_representation( + repre_doc = get_representation_by_id( project_name, - representation_id=active["representation"], + active["representation"], fields=["parent"] ) From aa91db6883485f33ba7bd2c74e2250ec5ebd906d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:36:54 +0200 Subject: [PATCH 087/258] added docstring for get_representations --- openpype/client/entities.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 7abb25e380..6c0170ea40 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -147,7 +147,7 @@ def get_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -291,11 +291,11 @@ def get_subsets( Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. Filter ignored if 'None' is passed. subset_names (list[str]): Subset names that should be queried. Filter ignored if 'None' is passed. - asset_ids (list[str, ObjectId]): Asset ids under which should look for + asset_ids (list[str|ObjectId]): Asset ids under which should look for the subsets. Filter ignored if 'None' is passed. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -339,7 +339,7 @@ def get_subset_families(project_name, subset_ids=None): Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. All subsets from project are used if 'None' is passed. Returns: @@ -476,7 +476,7 @@ def get_versions( Args: project_name (str): Name of project where to look for queried entities. - version_ids (list[str, ObjectId]): Version ids that will be queried. + version_ids (list[str|ObjectId]): Version ids that will be queried. Filter ignored if 'None' is passed. subset_ids (list[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. @@ -789,6 +789,32 @@ def get_representations( archived=False, fields=None ): + """Representaion entities data from one project filtered by filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + check_site_name (bool): Filter only representation that have existing + site name. + archived (bool): Output will also contain archived representations. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + repre_types = ["representation"] if archived: repre_types.append("archived_representations") From 7d13ba2706d0cdc11f6b6358bafc432c87522d04 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:37:09 +0200 Subject: [PATCH 088/258] added missing functions to legacy_io and mongodb --- openpype/pipeline/legacy_io.py | 9 +++++++++ openpype/pipeline/mongodb.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c8e7e79600..9359e3057b 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -144,3 +144,12 @@ def parenthood(*args, **kwargs): @requires_install def bulk_write(*args, **kwargs): return _connection_object.bulk_write(*args, **kwargs) + + +@requires_install +def active_project(*args, **kwargs): + return _connection_object.active_project(*args, **kwargs) + + +def current_project(*args, **kwargs): + return Session.get("AVALON_PROJECT") diff --git a/openpype/pipeline/mongodb.py b/openpype/pipeline/mongodb.py index 565e26b966..dab5bb9e13 100644 --- a/openpype/pipeline/mongodb.py +++ b/openpype/pipeline/mongodb.py @@ -199,6 +199,10 @@ class AvalonMongoDB: """Return the name of the active project""" return self.Session["AVALON_PROJECT"] + def current_project(self): + """Currently set project in Session without triggering installation.""" + return self.Session.get("AVALON_PROJECT") + @requires_install @auto_reconnect def projects(self, projection=None, only_active=True): From 0ce3045ad0bf399389cdbc7605762ecfe7b638f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:42 +0200 Subject: [PATCH 089/258] added missing docstrings --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 88 ++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2fa7730839..16b1dcf321 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -30,6 +30,7 @@ from .entities import ( get_representations_parents, get_thumbnail, + get_thumbnails, get_thumbnail_id_from_source, ) @@ -65,5 +66,6 @@ __all__ = ( "get_representations_parents", "get_thumbnail", + "get_thumbnails", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 6c0170ea40..c50ae32d7f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -866,6 +866,20 @@ def get_representations( def get_representations_parents(project_name, representations): + """Prepare parents of representation entities. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representations (list[dict]): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + repres_by_version_id = collections.defaultdict(list) versions_by_version_id = {} versions_by_subset_id = collections.defaultdict(list) @@ -921,15 +935,43 @@ def get_representations_parents(project_name, representations): def get_representation_parents(project_name, representation): + """Prepare parents of representation entity. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representation (dict): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + if not representation: return None repre_id = representation["_id"] - parents_by_repre_id = get_representations(project_name, [representation]) + parents_by_repre_id = get_representations_parents( + project_name, [representation] + ) return parents_by_repre_id.get(repre_id) def get_thumbnail_id_from_source(project_name, src_type, src_id): + """Receive thumbnail id from source entity. + + Args: + project_name (str): Name of project where to look for queried entities. + src_type (str): Type of source entity ('asset', 'version'). + src_id (str|objectId): Id of source entity. + + Returns: + ObjectId: Thumbnail id assigned to entity. + None: If Source entity does not have any thumbnail id assigned. + """ + if not src_type or not src_id: return None @@ -942,12 +984,54 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): return None +def get_thumbnails(project_name, thumbnail_ids, fields=None): + """Receive thumbnails entity data. + + Thumbnail entity can be used to receive binary content of thumbnail based + on it's content and ThumbnailResolvers. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_ids (list[str|ObjectId]): Ids of thumbnail entities. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + cursor: Cursor of queried documents. + """ + + if thumbnail_ids: + thumbnail_ids = _convert_ids(thumbnail_ids) + + if not thumbnail_ids: + return [] + query_filter = { + "type": "thumbnail", + "_id": {"$in": thumbnail_ids} + } + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + def get_thumbnail(project_name, thumbnail_id, fields=None): + """Receive thumbnail entity data. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_id (str|ObjectId): Id of thumbnail entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If thumbnail with specified id was not found. + Dict: Thumbnail entity data which can be reduced to specified 'fields'. + """ + if not thumbnail_id: return None query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} conn = _get_project_connection(project_name) - return conn.find(query_filter, _prepare_fields(fields)) + return conn.find_one(query_filter, _prepare_fields(fields)) """ From 125af17c2244f902447048c803d649f657f16f38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:47 +0200 Subject: [PATCH 090/258] updated usages --- openpype/client/entities.py | 57 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index c50ae32d7f..2459ea3e92 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1045,12 +1045,12 @@ openpype/tools/assetlinks/widgets.py - get_versions - get_subsets - get_assets - - get_version_links + - get_output_link_versions openpype/tools/creator/window.py - CreatorWindow Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/launcher/models.py @@ -1093,12 +1093,14 @@ openpype/tools/loader/widgets.py - get_subset_families - VersionTextEdit Query: - - get_subset - - get_version + - get_subset_by_id + - get_version_by_id - SubsetWidget Query: - get_subsets - get_representations + Update: + - Subset groups (combination of asset id and subset names) - RepresentationWidget Query: - get_subsets @@ -1112,12 +1114,12 @@ openpype/tools/loader/widgets.py openpype/tools/mayalookassigner/app.py - MayaLookAssignerWindow Query: - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/mayalookassigner/commands.py - create_items_from_nodes Query: - - get_asset + - get_asset_by_id openpype/tools/mayalookassigner/vray_proxies.py - get_look_relationships @@ -1128,7 +1130,7 @@ openpype/tools/mayalookassigner/vray_proxies.py - get_representation_by_name - vrayproxy_assign_look Query: - - get_last_version_for_subset + - get_last_version_by_subset_name openpype/tools/project_manager/project_manager/model.py - HierarchyModel @@ -1150,7 +1152,7 @@ openpype/tools/project_manager/project_manager/widgets.py openpype/tools/publisher/widgets/create_dialog.py - CreateDialog Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/publisher/control.py @@ -1161,18 +1163,18 @@ openpype/tools/publisher/control.py openpype/tools/sceneinventory/model.py - InventoryModel Query: - - get_asset - - get_subset - - get_version - - get_last_version_for_subset + - get_asset_by_id + - get_subset_by_id + - get_version_by_id + - get_last_version_by_subset_id - get_representation openpype/tools/sceneinventory/switch_dialog.py - SwitchAssetDialog Query: - - get_asset + - get_asset_by_name - get_assets - - get_subset + - get_subset_by_name - get_subsets - get_versions - get_hero_versions @@ -1182,10 +1184,10 @@ openpype/tools/sceneinventory/switch_dialog.py openpype/tools/sceneinventory/view.py - SceneInventoryView Query: - - get_version + - get_version_by_id - get_versions - get_hero_versions - - get_representation + - get_representation_by_id - get_representations openpype/tools/standalonepublish/widgets/model_asset.py @@ -1197,31 +1199,31 @@ openpype/tools/standalonepublish/widgets/widget_asset.py - AssetWidget Query: - get_project - - get_asset + - get_asset_by_id openpype/tools/standalonepublish/widgets/widget_family.py - FamilyWidget Query: - - get_asset - - get_subset + - get_asset_by_name + - get_subset_by_name - get_subsets - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/standalonepublish/app.py - Window Query: - - get_asset + - get_asset_by_id openpype/tools/texture_copy/app.py - TextureCopy Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/files_widget.py - FilesWidget Query: - - get_asset + - get_asset_by_id openpype/tools/workfiles/model.py - PublishFilesModel @@ -1234,12 +1236,13 @@ openpype/tools/workfiles/save_as_dialog.py - build_workfile_data Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/window.py - Window Query: - - get_asset + - get_asset_by_id + - get_asset_by_name openpype/tools/utils/assets_widget.py - AssetModel @@ -1259,11 +1262,11 @@ openpype/tools/utils/lib.py - get_project - FamilyConfigCache Query: - - get_asset + - get_asset_by_name openpype/tools/utils/tasks_widget.py - TasksModel Query: - get_project - - get_asset + - get_asset_by_id """ From 10bf3a33e1c90104b5f9cfe709b46607a5c98807 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 11:45:12 +0200 Subject: [PATCH 091/258] Nuke: no need to add Nuke host to filter It is actually adding review even not needed --- openpype/settings/defaults/project_settings/deadline.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..a6e7b4a94a 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -83,9 +83,6 @@ "maya": [ ".*([Bb]eauty).*" ], - "nuke": [ - ".*" - ], "aftereffects": [ ".*" ], From 082fb1d29dfe151809c8a63767a9fc941462a23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 8 Jun 2022 12:09:13 +0200 Subject: [PATCH 092/258] :sparkles: settings for camera content validator --- .../publish/validate_camera_contents.py | 3 +++ .../defaults/project_settings/maya.json | 5 ++++ .../schemas/schema_maya_publish.json | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 20af8d2315..38cf65f006 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -20,6 +20,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Camera Contents' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + validate_shapes = True @classmethod def get_invalid(cls, instance): @@ -49,6 +50,8 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") + if not cls.validate_shapes: + return # non-camera shapes valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) shapes = set(shapes) - set(valid_shapes) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index efd22e13c8..b39beeb305 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -413,6 +413,11 @@ "optional": true, "active": true }, + "ValidateCameraContents": { + "enabled": true, + "optional": true, + "validate_shapes": true + }, "ExtractPlayblast": { "capture_preset": { "Codec": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 9877b5ff0d..dca0e85acb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -570,6 +570,30 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ValidateCameraContents", + "label": "Validate Camera Content", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "validate_shapes", + "label": "Validate presence of shapes" + } + ] + }, { "type": "splitter" }, From 860dc3fc90ae78a044bbbf52e47560e1d7cdd7ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 14:57:12 +0200 Subject: [PATCH 093/258] use temp file instead of clipboard for node duplication --- openpype/hosts/nuke/api/lib.py | 44 +++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3062091ba0..7329fc859b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3,6 +3,8 @@ from pprint import pformat import re import six import platform +import time +import tempfile import contextlib from collections import OrderedDict @@ -2562,20 +2564,50 @@ class DirmapCache: return cls._sync_module +@contextlib.contextmanager +def _duplicate_node_temp(): + """Create a temp file where node is pasted during duplication. + + This is to avoid using clipboard for node duplication. + """ + + duplicate_node_temp_path = os.path.join( + tempfile.gettempdir(), + "openpype_nuke_duplicate_temp_{}".format(os.getpid()) + ) + + # This can happen only if 'duplicate_node' would be + if os.path.exists(duplicate_node_temp_path): + log.warning(( + "Temp file for node duplication already exists." + " Trying to remove {}" + ).format(duplicate_node_temp_path)) + os.remove(duplicate_node_temp_path) + + try: + # Yield the path where node can be copied + yield duplicate_node_temp_path + + finally: + # Remove the file at the end + os.remove(duplicate_node_temp_path) + + def duplicate_node(node): reset_selection() # select required node for duplication node.setSelected(True) - # copy selected to clipboard - nuke.nodeCopy("%clipboard%") + with _duplicate_node_temp() as filepath: + # copy selected to temp filepath + nuke.nodeCopy(filepath) - # reset selection - reset_selection() + # reset selection + reset_selection() - # paste node and selection is on it only - dupli_node = nuke.nodePaste("%clipboard%") + # paste node and selection is on it only + dupli_node = nuke.nodePaste(filepath) # reset selection reset_selection() From 1f8be47064249bb69ca764a70e18f81d2cb3af49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 14:57:42 +0200 Subject: [PATCH 094/258] remove unused import --- openpype/hosts/nuke/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7329fc859b..4391742f43 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3,7 +3,6 @@ from pprint import pformat import re import six import platform -import time import tempfile import contextlib from collections import OrderedDict From 63a83dc23a7075bb40f2ea827c24aa0b0dbf3088 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Jun 2022 16:04:55 +0200 Subject: [PATCH 095/258] Added set_project method to widget It makes sending of project name to model clearer. --- openpype/modules/sync_server/tray/app.py | 9 +++------ openpype/modules/sync_server/tray/widgets.py | 3 +++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index dee3bf0ecc..96fad6a247 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -101,8 +101,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.representationWidget = repres def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + self.representationWidget.set_project(self.projects.current_project) self.projects.refresh() self._set_running(True) super().showEvent(event) @@ -115,9 +114,7 @@ class SyncServerWindow(QtWidgets.QDialog): if self.projects.current_project is None: return - self.representationWidget.table_view.model().set_project( - self.projects.current_project - ) + self.representationWidget.set_project(self.projects.current_project) project_name = self.projects.current_project if not self.sync_server.get_sync_project_setting(project_name): @@ -131,7 +128,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() - self.representationWidget.model.set_project(None) + self.representationWidget.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 89ab12ea4d..b4ee447ac4 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -253,6 +253,9 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() # active index changed message_generated = QtCore.Signal(str) + def set_project(self, project): + self.model.set_project(project) + def _selection_changed(self, _new_selected, _all_selected): idxs = self.selection_model.selectedRows() self._selected_ids = set() From ca6d77fff79edd8d7073b6b36ce17990ce22edc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 8 Jun 2022 16:25:51 +0200 Subject: [PATCH 096/258] :art: modify camera extractor to allow more data --- .../plugins/publish/extract_camera_alembic.py | 17 +++++++++----- .../publish/extract_camera_mayaScene.py | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index 5ad6b79d5c..4110ad474d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -34,7 +34,6 @@ class ExtractCameraAlembic(openpype.api.Extractor): dag=True, type="camera") # validate required settings - assert len(cameras) == 1, "Not a single camera found in extraction" assert isinstance(step, float), "Step must be a float value" camera = cameras[0] @@ -44,8 +43,12 @@ class ExtractCameraAlembic(openpype.api.Extractor): path = os.path.join(dir_path, filename) # Perform alembic extraction + member_shapes = cmds.ls( + members, leaf=True, shapes=True, long=True, dag=True) with lib.maintained_selection(): - cmds.select(camera, replace=True, noExpand=True) + cmds.select( + member_shapes, + replace=True, noExpand=True) # Enforce forward slashes for AbcExport because we're # embedding it into a job string @@ -57,10 +60,12 @@ class ExtractCameraAlembic(openpype.api.Extractor): job_str += ' -step {0} '.format(step) if bake_to_worldspace: - transform = cmds.listRelatives(camera, - parent=True, - fullPath=True)[0] - job_str += ' -worldSpace -root {0}'.format(transform) + job_str += ' -worldSpace' + for member in member_shapes: + self.log.info(f"processing {member}") + transform = cmds.listRelatives( + member, parent=True, fullPath=True)[0] + job_str += ' -root {0}'.format(transform) job_str += ' -file "{0}"'.format(path) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 49c156f9cd..1cb30e65ea 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -131,12 +131,12 @@ class ExtractCameraMayaScene(openpype.api.Extractor): "bake to world space is ignored...") # get cameras - members = instance.data['setMembers'] + members = cmds.ls(instance.data['setMembers'], leaf=True, shapes=True, + long=True, dag=True) cameras = cmds.ls(members, leaf=True, shapes=True, long=True, dag=True, type="camera") # validate required settings - assert len(cameras) == 1, "Single camera must be found in extraction" assert isinstance(step, float), "Step must be a float value" camera = cameras[0] transform = cmds.listRelatives(camera, parent=True, fullPath=True) @@ -158,15 +158,24 @@ class ExtractCameraMayaScene(openpype.api.Extractor): frame_range=[start, end], step=step ) - baked_shapes = cmds.ls(baked, + baked_camera_shapes = cmds.ls(baked, type="camera", dag=True, shapes=True, long=True) + + members = members + baked_camera_shapes + members.remove(camera) else: - baked_shapes = cameras + baked_camera_shapes = cmds.ls(cameras, + type="camera", + dag=True, + shapes=True, + long=True) # Fix PLN-178: Don't allow background color to be non-black - for cam in baked_shapes: + for cam in cmds.ls( + baked_camera_shapes, type="camera", dag=True, + shapes=True, long=True): attrs = {"backgroundColorR": 0.0, "backgroundColorG": 0.0, "backgroundColorB": 0.0, @@ -177,7 +186,8 @@ class ExtractCameraMayaScene(openpype.api.Extractor): cmds.setAttr(plug, value) self.log.info("Performing extraction..") - cmds.select(baked_shapes, noExpand=True) + cmds.select(cmds.ls(members, dag=True, + shapes=True, long=True), noExpand=True) cmds.file(path, force=True, typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 From 248e38b52d1bfbd14226fbe79a12d4f2f5a8a016 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:30 +0200 Subject: [PATCH 097/258] Deadline: adding condition for `review` instance data key to be able to pass review to metadata json from nuke --- .../deadline/plugins/publish/submit_publish_job.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..1891731c93 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -640,6 +640,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") + + # test also instance data review attribute + preview = preview or instance.get("review") + # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -749,6 +753,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) + # also include review attribute if available + if "review" in data: + instance_skeleton_data["review"] = data["review"] + # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 6758bcc2a2aec0139ed2a448daba3c08ea60a584 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:55 +0200 Subject: [PATCH 098/258] Nuke: wrong name in docstring --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 50a5d01483..057bca11ac 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -7,7 +7,7 @@ import clique class NukeRenderLocal(openpype.api.Extractor): # TODO: rewrite docstring to nuke - """Render the current Fusion composition locally. + """Render the current Nuke composition locally. Extract the result of savers by starting a comp render This will run the local render of Fusion. From e4cee4a98ddd4150ee371d338350aefc4964df49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:20 +0200 Subject: [PATCH 099/258] fix loader --- openpype/tools/loader/model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index e8e0480d9c..f2b7e9a6a4 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -206,9 +206,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if subset_doc_projection: self.subset_doc_projection = subset_doc_projection - self.asset_doc_projection = asset_doc_projection - self.subset_doc_projection = subset_doc_projection - self.repre_icons = {} self.sync_server = None self.active_site = self.active_provider = None From 1cea16b97b416cda2cd12fca9365b046bc05a2eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:27 +0200 Subject: [PATCH 100/258] fix launcher --- openpype/tools/launcher/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 307f591d96..3f899cc05e 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -651,9 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = get_assets( - self._last_project_name, fields=list(self._asset_projection.keys()) - ) + asset_docs = list(get_assets( + self._last_project_name, fields=self._asset_projection.keys() + )) if not self._refreshing_assets: return self._refreshing_assets = False From 6fcf8722370fcbdf7fabba7dcc1012f8484f836a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:20:53 +0200 Subject: [PATCH 101/258] fix get_last_version --- openpype/client/entities.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 2459ea3e92..66204a4b19 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -655,6 +655,10 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] + fields = _prepare_fields(fields) + if fields and "parent" not in fields: + fields.append("parent") + version_docs = get_versions( project_name, version_ids=version_ids, fields=fields ) From a1e95dd15ad7c4e73837262b8f2de3bf543213c4 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 10:58:18 +0300 Subject: [PATCH 102/258] Fix oiio subprocess arguments. --- openpype/plugins/publish/extract_jpeg_exr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index f474714780..e509063dca 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,9 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) + self.log.info(f"running: {oiio_cmd}") + run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From f79f9347ece5db8001608b595cff4cee67e16164 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Thu, 9 Jun 2022 11:12:48 +0300 Subject: [PATCH 103/258] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..0bbfd95862 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,5 +161,5 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + subprocess_command, shell=True, logger=self.log + ) From 012f21ce86e12daeee549830b4cbfdca03991709 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:17:01 +0300 Subject: [PATCH 104/258] Fix error messages. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..874a1dc40d 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -83,12 +83,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info("Input can be read by OIIO, converting with oiiotool now.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - self.log.info("converting with") + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: - + self.log.warning("Thumbanil has not been created.") return new_repre = { From 5e817c57e4c46087542fdf85b8866d063e80c933 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:18:14 +0300 Subject: [PATCH 105/258] Fix style warning. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c40c99b1f8..730d0167c7 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -80,10 +80,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg if returncode == 0: - self.log.info("Input can be read by OIIO, converting with oiiotool now.") + self.log.info("Input can be read by OIIO, converting with oiiotool now.") # noqa thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - self.log.info("Converting with FFMPEG because input can't be read by OIIO.") + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") # noqa thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created From 5e0589dc57e77f813a32f95afcfc8f508d7cf2ec Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:20:27 +0300 Subject: [PATCH 106/258] Restore removed logging messaage. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 730d0167c7..c658e17cab 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,7 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - self.log.info(f"running: {oiio_cmd}") + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): From 6b9a1b834d3de4f1d2b399c916a50ac1282a4b6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:48:29 +0200 Subject: [PATCH 107/258] convert queried cursor to list in scene inventory --- openpype/tools/sceneinventory/view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 8164c48a5d..0b54d40ed7 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -669,11 +669,11 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - version_docs = get_versions( + version_docs = list(get_versions( project_name, subset_ids=[repre_version_doc["parent"]], hero=True - ) + )) hero_version = None standard_versions = [] for version_doc in version_docs: From 39ea438d7d8c2010b914c300ae84f6536e71e0a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:51:33 +0200 Subject: [PATCH 108/258] fix typo in passed kwarg --- openpype/tools/sceneinventory/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 0b54d40ed7..63d181b2d6 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -92,7 +92,7 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() repre_docs = get_representations( - project_name, representaion_ids=repre_ids, fields=["parent"] + project_name, representation_ids=repre_ids, fields=["parent"] ) version_ids = [] From a48d77e609f84a7a04bf5de95fce12e05461ca6c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:53:15 +0200 Subject: [PATCH 109/258] conver repres cursor to list --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 92ef2b3553..aa4dcd73cd 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -165,11 +165,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_loaders.add(item["loader"]) project_name = self.active_project() - repres = get_representations( + repres = list(get_representations( project_name, representation_ids=repre_ids, archived=True - ) + )) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation From bae1e38112cc8a756388b92f1447c5e69112e6ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:59:04 +0200 Subject: [PATCH 110/258] fix kwargs name in switch dialog --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index aa4dcd73cd..1d1d5cbb91 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -944,7 +944,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = list( get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) ) @@ -1102,7 +1102,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) repres_by_subset_name = {} From 57e72c2c3722f232b58a81454b3f46dbd7cba647 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:32:49 +0200 Subject: [PATCH 111/258] minor changesminor changes in representation widget --- openpype/tools/loader/model.py | 23 +++++++++++------------ openpype/tools/loader/widgets.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f2b7e9a6a4..f030e94256 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -231,7 +231,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() @@ -250,7 +250,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def set_grouping(self, state): self._grouping = state - self.on_doc_fetched() + self._on_doc_fetched() def get_subsets_families(self): return self._doc_payload.get("subset_families") or set() @@ -528,7 +528,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.fetch_subset_and_version() - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self._items_by_id = {} self.beginResetModel() @@ -1019,7 +1019,6 @@ class RepresentationSortProxyModel(GroupMemberFilterProxyModel): class RepresentationModel(TreeModel, BaseRepresentationModel): - doc_fetched = QtCore.Signal() refreshed = QtCore.Signal(bool) @@ -1055,12 +1054,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'files.sites': 1 } - def __init__(self, dbcon, header, version_ids): + def __init__(self, dbcon, header): super(RepresentationModel, self).__init__() self.dbcon = dbcon self._data = [] self._header = header - self.version_ids = version_ids + self._version_ids = [] manager = ModulesManager() sync_server = active_site = remote_site = None @@ -1092,7 +1091,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self.remote_site = remote_site self.remote_provider = remote_provider - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self._docs = {} self._icons = lib.get_repre_icons() @@ -1103,7 +1102,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self._items_by_id = {} def set_version_ids(self, version_ids): - self.version_ids = version_ids + self._version_ids = version_ids self.refresh() def data(self, index, role): @@ -1171,7 +1170,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return super(RepresentationModel, self).data(index, role) - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self.beginResetModel() subsets = set() @@ -1181,7 +1180,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): group = None self._items_by_id = {} for doc in self._docs: - if len(self.version_ids) > 1: + if len(self._version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: group_item = Item() @@ -1265,12 +1264,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return repre_docs = [] - if self.version_ids: + if self._version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python repre_docs = list(get_representations( project_name, - version_ids=self.version_ids, + version_ids=self._version_ids, check_site_name=True, fields=self.repre_projection.keys() )) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 921708922e..fd43435b15 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1164,7 +1164,7 @@ class RepresentationWidget(QtWidgets.QWidget): headers = [item[0] for item in self.default_widths] - model = RepresentationModel(self.dbcon, headers, []) + model = RepresentationModel(self.dbcon, headers) proxy_model = RepresentationSortProxyModel(self) proxy_model.setSourceModel(model) @@ -1231,7 +1231,7 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - project_name = self.dbcon.actual_project() + project_name = self.dbcon.active_project() repre_docs = get_representations( project_name, representation_ids=repre_ids, From de2fb5d1ff808d81a56a84c8240cac2465feae42 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:18 +0200 Subject: [PATCH 112/258] convert cursor of representations to list --- openpype/tools/loader/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index fd43435b15..0482bad642 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1232,11 +1232,11 @@ class RepresentationWidget(QtWidgets.QWidget): repre_ids.append(item["_id"]) project_name = self.dbcon.active_project() - repre_docs = get_representations( + repre_docs = list(get_representations( project_name, representation_ids=repre_ids, fields=["name", "parent"] - ) + )) version_ids = [ repre_doc["parent"] From 2df00d5caefd92aad61112b110d8b7a9303f5230 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:43 +0200 Subject: [PATCH 113/258] remove site name check in representations widget --- openpype/client/entities.py | 6 ------ openpype/tools/loader/model.py | 1 - 2 files changed, 7 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 66204a4b19..91646e7a1d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -789,7 +789,6 @@ def get_representations( version_ids=None, extensions=None, names_by_version_ids=None, - check_site_name=False, archived=False, fields=None ): @@ -809,8 +808,6 @@ def get_representations( file (without dot). names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. - check_site_name (bool): Filter only representation that have existing - site name. archived (bool): Output will also contain archived representations. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -827,9 +824,6 @@ def get_representations( else: query_filter = {"type": {"$in": repre_types}} - if check_site_name: - query_filter["files.site.name"] = {"$exists": True} - if representation_ids is not None: representation_ids = _convert_ids(representation_ids) if not representation_ids: diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f030e94256..46acc68f67 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -1270,7 +1270,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_docs = list(get_representations( project_name, version_ids=self._version_ids, - check_site_name=True, fields=self.repre_projection.keys() )) From 483ca9b301680cdfee8eee7f4270eb5a1654f312 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:49:03 +0200 Subject: [PATCH 114/258] fix args order --- openpype/tools/loader/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 46acc68f67..b5dc16a680 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -267,7 +267,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): else: project_name = self.dbcon.active_project() version_doc = get_version_by_name( - project_name, subset_id, value + project_name, value, subset_id ) # update availability on active site when version changes From a63e5592ab02c61c3712d6cc0a10716ffb2efb33 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:52:59 +0200 Subject: [PATCH 115/258] fixed asset links widget --- openpype/tools/assetlinks/widgets.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 3078585ed1..1b168e542c 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -74,13 +74,13 @@ class SimpleLinkView(QtWidgets.QWidget): fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), @@ -117,13 +117,13 @@ class SimpleLinkView(QtWidgets.QWidget): version_doc["_id"], fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), From 242b9f5f3a9ed734743d7b88cba7191be4841fa3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 00:49:01 +0200 Subject: [PATCH 116/258] added module setting and tray item --- openpype/modules/ftrack/tray/ftrack_tray.py | 44 ++++++++++++++----- .../defaults/system_settings/modules.json | 14 ++++++ .../module_settings/schema_ftrack.json | 37 ++++++++++++++++ tools/run_ftrack_eventserver.ps1 | 39 ++++++++++++++++ 4 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 tools/run_ftrack_eventserver.ps1 diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index c6201a94f6..699b33e187 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,6 +2,8 @@ import os import time import datetime import threading +import platform +from subprocess import Popen from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -12,6 +14,7 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources +from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -42,12 +45,25 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) + self.icon_ftrackapp = QtGui.QIcon( + resources.get_resource("icons", "inventory.png") + ) def show_login_widget(self): self.widget_login.show() self.widget_login.activateWindow() self.widget_login.raise_() + def show_ftrack_browser(self): + am = get_system_settings() + browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] + browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] + if "=" not in browser_arg: + browser_arg = '{:1}'.format(browser_arg) + cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + log.info(f"Opening Ftrack Browser: {cmd}") + Popen(cmd) + def validate(self): validation = False cred = credentials.get_credentials() @@ -251,16 +267,12 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + browser_open.setIcon(self.icon_ftrackapp) + tray_menu.addAction(browser_open) + self.browser_open = browser_open # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -284,6 +296,18 @@ class FtrackTrayWrapper: tray_server_menu.addAction(self.action_server_stop) self.tray_server_menu = tray_server_menu + + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials + self.bool_logged = False self.set_menu_visibility() diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..aaf01b1631 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,6 +15,20 @@ "ftrack": { "enabled": false, "ftrack_server": "", + "ftrack_browser_path": { + "windows": [ + "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" + ], + "darwin": [], + "linux": [] + }, + "ftrack_browser_arguments": { + "windows": [ + "--app=" + ], + "darwin": [], + "linux": [] + }, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 654ddf2938..f30d536052 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -19,6 +19,43 @@ { "type": "splitter" }, + { + "type": "path", + "key": "ftrack_browser_path", + "label": "Browser Path", + "use_label_wrap": true, + "multipath": true, + "multiplatform": true + }, + { + "type": "dict", + "key": "ftrack_browser_arguments", + "label": "Browser Arguments", + "use_label_wrap": true, + "children": [ + { + "key": "windows", + "label": "Windows", + "type": "list", + "object_type": "text" + }, + { + "key": "darwin", + "label": "MacOS", + "type": "list", + "object_type": "text" + }, + { + "key": "linux", + "label": "Linux", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "splitter" + }, { "type": "label", "label": "Additional Ftrack event handlers paths" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 new file mode 100644 index 0000000000..9c22f3d88e --- /dev/null +++ b/tools/run_ftrack_eventserver.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Helper script to start OpenPype Ftrack EventServer without relying on built executables. + +.DESCRIPTION + + +.EXAMPLE + +PS> .\run_eventserver.ps1 + +#> +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" +$env:OPENPYPE_DEBUG = "1" +# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file From 3a7e329e85793962b7b15a70df33e5930147f94e Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:30 +0300 Subject: [PATCH 117/258] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..b336787599 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,6 +161,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + try: + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using ffmpeg", + exc_info=True + ) + return False From 28d0ea37a7523c2a4560d21ec07fda68d04bc89f Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:42 +0300 Subject: [PATCH 118/258] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index b336787599..c1f7f38024 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -132,7 +132,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") - run_subprocess(oiio_cmd, logger=self.log) + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using oiiotool", + exc_info=True + ) + return False def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From 51fb0385f81aefe0ebc71a4f8256b19976e8fce0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 12:16:31 +0200 Subject: [PATCH 119/258] mapped hosts queries --- openpype/client/entities.py | 294 +++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91646e7a1d..861ac300e5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1033,10 +1033,302 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ -Custom data storage: +## Custom data storage: - Webpublisher - jobs - Ftrack - events +- Maya - Shaders + - openpype/hosts/maya/api/shader_definition_editor.py + - openpype/hosts/maya/plugins/publish/validate_model_name.py +## Global launch hooks +- openpype/hooks/pre_global_host_data.py + Query: + - project + - asset + +## Hosts +### Aftereffects +- openpype/hosts/aftereffects/plugins/create/workfile_creator.py + Query: + - asset + +### Blender +- openpype/hosts/blender/api/pipeline.py + Query: + - asset +- openpype/hosts/blender/plugins/publish/extract_layout.py + Query: + - representation + +### Celaction +- openpype/hosts/celaction/plugins/publish/collect_audio.py + Query: + - subsets + - last versions + - representations + +### Fusion +- openpype/hosts/fusion/api/lib.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/fusion/plugins/load/load_sequence.py + Query: + - version +- openpype/hosts/fusion/scripts/fusion_switch_shot.py + Query: + - project + - asset + - versions +- openpype/hosts/fusion/utility_scripts/switch_ui.py + Query: + - assets + +### Harmony +- openpype/hosts/harmony/api/pipeline.py + Query: + - representation + +### Hiero +- openpype/hosts/hiero/api/lib.py + Query: + - project + - version + - versions + - representation +- openpype/hosts/hiero/api/tags.py + Query: + - task types + - assets +- openpype/hosts/hiero/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py + Query: + - assets + +### Houdini +- openpype/hosts/houdini/api/lib.py + Query: + - asset +- openpype/hosts/houdini/api/usd.py + Query: + - asset +- openpype/hosts/houdini/plugins/create/create_hda.py + Query: + - asset + - subsets +- openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py + Query: + - asset + - subset +- openpype/hosts/houdini/plugins/publish/extract_usd_layered.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py + Query: + - asset + - subset +- openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py + Query: + - project + - asset + +### Maya +- openpype/hosts/maya/api/action.py + Query: + - asset +- openpype/hosts/maya/api/commands.py + Query: + - asset + - project +- openpype/hosts/maya/api/lib.py + Query: + - project + - asset + - subset + - subsets + - version + - representation +- openpype/hosts/maya/api/setdress.py + Query: + - version + - representation +- openpype/hosts/maya/plugins/inventory/import_modelrender.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_audio.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_image_plane.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_look.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_vrayproxy.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_yeti_cache.py + Query: + - representation +- openpype/hosts/maya/plugins/publish/collect_review.py + Query: + - subsets +- openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py + Query: + - assets +- openpype/hosts/maya/plugins/publish/validate_node_ids_related.py + Query: + - asset +- openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py + Query: + - asset + - subset + +### Nuke +- openpype/hosts/nuke/api/command.py + Query: + - project + - asset +- openpype/hosts/nuke/api/lib.py + Query: + - project + - asset + - version + - versions + - representation +- openpype/hosts/nuke/plugins/load/load_backdrop.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_camera_abc.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_image.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_model.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_script_precomp.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/publish/collect_reads.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_instances.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_writes.py + Query: + - representation +- openpype/hosts/nuke/plugins/publish/validate_script.py + Query: + - asset + - project + +### Photoshop +- openpype/hosts/photoshop/plugins/create/workfile_creator.py + Query: + - asset + +### Resolve +- openpype/hosts/resolve/plugins/load/load_clip.py + Query: + - version + - versions + +### Standalone publisher +- openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py + Query: + - asset +- openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py + Query: + - assets +- openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py + Query: + - project + - asset +- openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py + Query: + - assets + +### TVPaint +- openpype/hosts/tvpaint/api/pipeline.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/load/load_workfile.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_instances.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py + Query: + - asset + +### Unreal +- openpype/hosts/unreal/plugins/load/load_camera.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/load/load_layout.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/publish/extract_layout.py + Query: + - representation + +### Webpublisher +- openpype/hosts/webpublisher/webserver_service/webpublish_routes.py + Query: + - assets +- openpype/hosts/webpublisher/plugins/publish/collect_published_files.py + Query: + - last versions + +## Tools openpype/tools/assetlinks/widgets.py - SimpleLinkView Query: From 79db1b1c4354c95d4732f54f09bf2a2357c59572 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 12:40:57 +0200 Subject: [PATCH 120/258] flame: adding staging dir to `cleanupFullPaths` --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index f3239af23e..6ad8f8cf96 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -445,4 +445,6 @@ class ExtractSubsetResources(openpype.api.Extractor): ) instance.data['stagingDir'] = staging_dir + instance.context.data["cleanupFullPaths"].append(staging_dir) + return staging_dir From b19fc26cf68043574f704ecc3eac6703734bf33f Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 10 Jun 2022 14:52:54 +0300 Subject: [PATCH 121/258] Handle muiltilayered flag --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..907b3ea4af 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -126,7 +126,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() - oiio_cmd = [oiio_tool_path, + oiio_cmd = [oiio_tool_path, "-a", src_path, "-o", dst_path ] From 0ea1032a6114f80cee0a87c1d5736ebe293cefd9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:06:30 +0200 Subject: [PATCH 122/258] added global plugins --- openpype/client/entities.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 861ac300e5..b61577ac70 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1046,6 +1046,54 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): - project - asset +## Global load plugins +- openpype/plugins/load/delete_old_versions.py + Query: + - versions + - representations +- openpype/plugins/load/delivery.py + Query: + - representations + +## Global publish plugins +- openpype/plugins/publish/collect_avalon_entities.py + Query: + - asset + - project +- openpype/plugins/publish/collect_anatomy_instance_data.py + Query: + - assets + - subsets +- openpype/plugins/publish/collect_scene_loaded_versions.py + Query: + - representations +- openpype/plugins/publish/extract_hierarchy_avalon.py + Query: + - asset + - assets + - project + Create: + - asset + Update: + - asset +- openpype/plugins/publish/integrate_hero_version.py + Query: + - version + - hero version + - representations +- openpype/plugins/publish/integrate_new.py + Query: + - asset + - subset + - version + - representations +- openpype/plugins/publish/integrate_thumbnail.py + Query: + - version +- openpype/plugins/publish/validate_editorial_asset_name.py + Query: + - assets + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 22372361eb29d243ba16df4e83e827d1c8d54f62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:12:50 +0200 Subject: [PATCH 123/258] added pipeline queries --- openpype/client/entities.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index b61577ac70..82f16ff032 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1064,6 +1064,7 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets - subsets + - last version - openpype/plugins/publish/collect_scene_loaded_versions.py Query: - representations @@ -1094,6 +1095,23 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Pipeline +- openpype/pipeline/load/utils.py + Query: + - project + - assets + - subsets + - version + - versions + - representation + - representations +- openpype/pipeline/mongodb.py + Query: + - project +- openpype/pipeline/thumbnail.py + Query: + - project + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 956ad8299b652b38e77215b545454ddba3715d87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:19:18 +0200 Subject: [PATCH 124/258] mapped lib --- openpype/client/entities.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82f16ff032..82caaee7c5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1095,6 +1095,39 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Lib +- openpype/lib/applications.py + Query: + - project + - asset +- openpype/lib/avalon_context.py + Query: + - project + - asset + - linked assets (new function get_linked_assets?) + - subset + - subsets + - version + - versions + - last version + - representations + - linked representations (new function get_linked_ids_for_representations) + Update: + - workfile data +- openpype/lib/plugin_tools.py + Query: + - asset +- openpype/lib/project_backpack.py + Query: + - project + - everything from mongo + Update: + - project +- openpype/lib/usdlib.py + Query: + - project + - asset + ## Pipeline - openpype/pipeline/load/utils.py Query: From 924535a6ce57c3d07bb3cdb61cde92978edc9236 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:30:14 +0200 Subject: [PATCH 125/258] removed icon since not the same as other entries --- openpype/modules/ftrack/tray/ftrack_tray.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 699b33e187..54d3f3132f 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -45,9 +45,6 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) - self.icon_ftrackapp = QtGui.QIcon( - resources.get_resource("icons", "inventory.png") - ) def show_login_widget(self): self.widget_login.show() @@ -270,7 +267,6 @@ class FtrackTrayWrapper: # Ftrack Browser browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) browser_open.triggered.connect(self.show_ftrack_browser) - browser_open.setIcon(self.icon_ftrackapp) tray_menu.addAction(browser_open) self.browser_open = browser_open From 5e82c96a3da493de7259ffc122c21ecc617340c7 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:53:39 +0200 Subject: [PATCH 126/258] info label in settings, handles lists better --- openpype/modules/ftrack/tray/ftrack_tray.py | 17 +++++++++++------ .../module_settings/schema_ftrack.json | 4 ++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 54d3f3132f..30e1d9f983 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,12 +52,17 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - am = get_system_settings() - browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] - browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] - if "=" not in browser_arg: - browser_arg = '{:1}'.format(browser_arg) - cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + settings = get_system_settings() + browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_args.append(self.module.ftrack_url) + path = "" + for p in browser_paths: + if os.path.exists(p): + path = p + break + args = " ".join(str(item) for item in browser_args).replace("= ", "=") + cmd = f"{path} {args}" log.info(f"Opening Ftrack Browser: {cmd}") Popen(cmd) diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index f30d536052..268c5479fe 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -33,6 +33,10 @@ "label": "Browser Arguments", "use_label_wrap": true, "children": [ + { + "type": "label", + "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" + }, { "key": "windows", "label": "Windows", From 29e095e285468c9fafffcdaa004df576cc88d9f7 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 16:24:04 +0200 Subject: [PATCH 127/258] Remove avalon DB path --- openpype/modules/shotgrid/aop/patch.py | 52 ------------------- openpype/modules/shotgrid/shotgrid_module.py | 4 -- .../schemas/projects_schema/schema_main.json | 3 +- 3 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 openpype/modules/shotgrid/aop/patch.py diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py deleted file mode 100644 index 56477f3bf9..0000000000 --- a/openpype/modules/shotgrid/aop/patch.py +++ /dev/null @@ -1,52 +0,0 @@ -from copy import copy - - -from openpype.api import Logger -from openpype.modules.shotgrid.lib import ( - credentials, - settings, - server, -) - -_LOG = Logger().get_logger("ShotgridModule.patch") - - -def _patched_projects( - self, projection=None, only_active=True -): - all_projects = list(self._prev_projects(projection, only_active)) - if ( - not credentials.get_local_login() - or not settings.filter_projects_by_login() - ): - return all_projects - try: - linked_names = _fetch_linked_project_names() or set() - return [x for x in all_projects if _upper(x["name"]) in linked_names] - except Exception as e: - print(e) - return all_projects - - -def _upper(x): - return str(x).strip().upper() - - -def _fetch_linked_project_names(): - return { - _upper(x["project_name"]) - for x in server.find_linked_projects(credentials.get_local_login()) - } - - -def patch_avalon_db(): - _LOG.debug("Run avalon patching") - try: - from avalon.api import AvalonMongoDB - if AvalonMongoDB.projects is _patched_projects: - return None - _LOG.debug("Patch Avalon.projects method") - AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) - AvalonMongoDB.projects = _patched_projects - except Exception as e: - _LOG.error("Unable to patch avalon db {}".format(e)) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 6e0cbe987b..dcc8187194 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -46,10 +46,6 @@ class ShotgridModule( def tray_init(self): from .tray.shotgrid_tray import ShotgridTrayWrapper - from .aop.patch import patch_avalon_db - - patch_avalon_db() - threading.Timer(10.0, patch_avalon_db).start() self.tray_wrapper = ShotgridTrayWrapper(self) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 0ce3ddbefb..80b1baad1b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -64,11 +64,10 @@ }, { "type": "schema", - }, - { "name": "schema_project_shotgrid" }, { + "type": "schema", "name": "schema_project_kitsu" }, { From 4a45225ca27d175adc481a8899a3379bcab77dc1 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 16:55:50 +0200 Subject: [PATCH 128/258] moved menu entry to last position --- openpype/modules/ftrack/tray/ftrack_tray.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 30e1d9f983..4329b03b45 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -269,11 +269,16 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Ftrack Browser - browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) - browser_open.triggered.connect(self.show_ftrack_browser) - tray_menu.addAction(browser_open) - self.browser_open = browser_open + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -298,16 +303,11 @@ class FtrackTrayWrapper: self.tray_server_menu = tray_server_menu - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + tray_menu.addAction(browser_open) + self.browser_open = browser_open self.bool_logged = False self.set_menu_visibility() From 8add4b588d762d470a9e6138cb9c3c4542b75826 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:19:59 +0200 Subject: [PATCH 129/258] fixed checks and logged correct info --- openpype/modules/ftrack/tray/ftrack_tray.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 4329b03b45..e3df8eff12 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -3,7 +3,7 @@ import time import datetime import threading import platform -from subprocess import Popen +import subprocess from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -60,11 +60,18 @@ class FtrackTrayWrapper: for p in browser_paths: if os.path.exists(p): path = p + log.debug(f"Found valid executable at path: {p}") break + else: + log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + if path == "": + log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + return args = " ".join(str(item) for item in browser_args).replace("= ", "=") + log.debug(f"Computed arguments: {args}") cmd = f"{path} {args}" - log.info(f"Opening Ftrack Browser: {cmd}") - Popen(cmd) + log.debug(f"Opening Ftrack Browser...") + subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def validate(self): validation = False From 11fc8d26e70689f76bb6ddfac60923c70cd8bbc9 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:36:38 +0200 Subject: [PATCH 130/258] Line breaks as per hound's "suggestion" --- openpype/modules/ftrack/tray/ftrack_tray.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e3df8eff12..ee1e50f7f5 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -53,8 +53,10 @@ class FtrackTrayWrapper: def show_ftrack_browser(self): settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_paths = settings["modules"]["ftrack"]\ + ["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]\ + ["ftrack_browser_arguments"][platform.system().lower()] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: @@ -63,9 +65,11 @@ class FtrackTrayWrapper: log.debug(f"Found valid executable at path: {p}") break else: - log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + log.warning(f"Path: {p} is not valid, please \ + doublecheck your settings!") if path == "": - log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + log.warning("Found no valid executables to launch \ + Ftrack with. Feature will not work as expected!") return args = " ".join(str(item) for item in browser_args).replace("= ", "=") log.debug(f"Computed arguments: {args}") From 24a31e5e1058705d6d58fc8559fc42cfd8d661b8 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:49:16 +0200 Subject: [PATCH 131/258] fixed hound complaints hopefully --- openpype/modules/ftrack/tray/ftrack_tray.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index ee1e50f7f5..7ac994e967 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,11 +52,10 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]\ - ["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]\ - ["ftrack_browser_arguments"][platform.system().lower()] + cur_os = platform.system().lower() + settings = get_system_settings()["modules"]["ftrack"] + browser_paths = settings["ftrack_browser_path"][cur_os] + browser_args = settings["ftrack_browser_arguments"][cur_os] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: From 26b6bbab95c727b5f2dfcc07413c4d71d2d6f7a8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:18:50 +0200 Subject: [PATCH 132/258] update poetry lib --- poetry.lock | 70 ++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04be8c78f2..010dd57e17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.9.1" +version = "2.10.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" @@ -909,7 +909,7 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.0" +version = "3.20.1" description = "Protocol Buffers" category = "main" optional = false @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" +content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, - {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, + {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, + {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2444,30 +2444,30 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, - {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, - {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, - {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, - {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, - {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, - {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, - {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, - {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, - {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, - {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, - {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, - {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, - {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, - {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, - {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, + {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, + {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, + {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, + {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, + {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, + {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, + {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, + {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, + {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, + {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, + {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, + {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, + {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, + {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, + {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, + {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2891,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, From 8a06a4afc1866a5837b5cab7c4ad3845eb6ee050 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:25:40 +0200 Subject: [PATCH 133/258] remove unused import --- openpype/modules/shotgrid/shotgrid_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index dcc8187194..5644f0c35f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,5 +1,4 @@ import os -import threading from openpype_interfaces import ( ITrayModule, From 44b1b638e58e7fc3da13fc54ad100d980a44a1ca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 7 Jun 2022 15:58:30 +0200 Subject: [PATCH 134/258] Optimization: speed up validate mesh shader connections for large scenes (cherry picked from commit 46752511e500a54ba9335a325ca09224ef600227) --- .../validate_mesh_shader_connections.py | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index 0969573a90..e0835000f0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -12,28 +12,41 @@ def pairs(iterable): yield i, y -def get_invalid_sets(shape): - """Get sets that are considered related but do not contain the shape. +def get_invalid_sets(shapes): + """Return invalid sets for the given shapes. - In some scenarios Maya keeps connections to multiple shaders - even if just a single one is assigned on the full object. + This takes a list of shape nodes to cache the set members for overlapping + sets in the queries. This avoids many Maya set member queries. - These are related sets returned by `maya.cmds.listSets` that don't - actually have the shape as member. + Returns: + dict: Dictionary of shapes and their invalid sets, e.g. + {"pCubeShape": ["set1", "set2"]} """ - invalid = [] - sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] - for s in sets: - members = cmds.sets(s, query=True, nodesOnly=True) - if not members: - invalid.append(s) - continue + cache = dict() + invalid = dict() - members = set(cmds.ls(members, long=True)) - if shape not in members: - invalid.append(s) + # Collect the sets from the shape + for shape in shapes: + invalid_sets = [] + sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] + for set_ in sets: + + members = cache.get(set_, None) + if members is None: + members = set(cmds.ls(cmds.sets(set_, + query=True, + nodesOnly=True), long=True)) + cache[set_] = members + + # If the shape is not actually present as a member of the set + # consider it invalid + if shape not in members: + invalid_sets.append(set_) + + if invalid_sets: + invalid[shape] = invalid_sets return invalid @@ -92,15 +105,9 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): - shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True) - - # todo: allow to check anything that can have a shader - shapes = cmds.ls(shapes, noIntermediate=True, long=True, type="mesh") - - invalid = [] - for shape in shapes: - if get_invalid_sets(shape): - invalid.append(shape) + nodes = instance[:] + shapes = cmds.ls(nodes, noIntermediate=True, long=True, type="mesh") + invalid = get_invalid_sets(shapes).keys() return invalid @@ -108,7 +115,7 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): def repair(cls, instance): shapes = cls.get_invalid(instance) - for shape in shapes: - invalid_sets = get_invalid_sets(shape) + invalid = get_invalid_sets(shapes) + for shape, invalid_sets in invalid.items(): for set_node in invalid_sets: disconnect(shape, set_node) From 0163eae3fa80c720a527b134c1030362c4843ae5 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 13 Jun 2022 14:50:26 +0200 Subject: [PATCH 135/258] Remove unused code and add sugestions --- .../shotgrid/hooks/post_shotgrid_changes.py | 9 -------- openpype/modules/shotgrid/lib/settings.py | 23 ------------------- .../publish/collect_shotgrid_entities.py | 5 ++-- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py deleted file mode 100644 index e8369ad3cb..0000000000 --- a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py +++ /dev/null @@ -1,9 +0,0 @@ -from openpype.lib import PostLaunchHook - - -class PostShotgridHook(PostLaunchHook): - order = None - - def execute(self, *args, **kwargs): - print(args, kwargs) - pass diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4a772de5b7..924099f04b 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,24 +1,11 @@ -import os - -from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -from openpype.modules.shotgrid.lib.tools import memoize -def get_project_list(): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) - db = client['avalon'] - return db.list_collection_names() - - -@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) @@ -29,13 +16,3 @@ def get_shotgrid_servers(): def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") - - -def filter_projects_by_login(): - return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) - - -def get_shotgrid_event_mongo_info(): - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - collection_name = "shotgrid_events" - return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index a770c1eb87..9880425a41 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pymongo import MongoClient +from openpype.lib.mongo import OpenPypeMongoConnection from openpype.modules.shotgrid.lib.settings import ( get_shotgrid_project_settings, @@ -63,8 +63,7 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): def _get_shotgrid_collection(project): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) + client = OpenPypeMongoConnection.get_mongo_client() return client.get_database("shotgrid_openpype").get_collection(project) From cef804aa1670b755b5c0072a9f0d5990f6fa92d3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Mon, 13 Jun 2022 15:37:47 +0200 Subject: [PATCH 136/258] added eventserver utoility script for linux/mac --- tools/run_ftrack_eventserver.sh | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tools/run_ftrack_eventserver.sh diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh new file mode 100644 index 0000000000..97daa14c2d --- /dev/null +++ b/tools/run_ftrack_eventserver.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +art () { + cat <<-EOF + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + +EOF +} + +# Colors for terminal + +RST='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +# Main +main () { + echo -e "${BGreen}" + art + echo -e "${RST}" + + # Directories + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + + pushd "$openpype_root" > /dev/null || return > /dev/null + + echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." + "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver +} + +main From 27b8f77855d45446af3b7b742a58d30c2143d32f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:21 +0200 Subject: [PATCH 137/258] fix scene inventory --- openpype/tools/sceneinventory/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 117bdfcba1..0bb9c4a658 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -198,12 +198,12 @@ class InventoryModel(TreeModel): self.clear() if self._hierarchy_view and selected: - if not hasattr(host.pipeline, "update_hierarchy"): # If host doesn't support hierarchical containers, then # cherry-pick only. self.add_items((item for item in items if item["objectName"] in selected)) + return # Update hierarchy info for all containers items_by_name = {item["objectName"]: item From cd6c37ece91caf7af0d40882b1a41f23190ebb38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:57 +0200 Subject: [PATCH 138/258] added remaining custom mongo calls --- openpype/client/entities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82caaee7c5..a56288c1e8 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1034,6 +1034,8 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ ## Custom data storage: +- Settings - OP settings overrides and local settings +- Logging - logs from PypeLogger - Webpublisher - jobs - Ftrack - events - Maya - Shaders From 9cb5372d69e30e35a441c952178bb1aa2802e5a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:29:09 +0200 Subject: [PATCH 139/258] use client functions to query data in standalone publisher plugins --- .../publish/collect_bulk_mov_instances.py | 11 +++--- .../plugins/publish/collect_hierarchy.py | 36 ++++++++++--------- .../plugins/publish/collect_matching_asset.py | 5 +-- .../publish/validate_task_existence.py | 20 ++++------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 3e7fb19c00..052a97af7d 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -3,7 +3,7 @@ import json import pyblish.api from openpype.lib import get_subset_name_with_asset_doc -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_name class CollectBulkMovInstances(pyblish.api.InstancePlugin): @@ -24,12 +24,9 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context + project_name = context.data["projectEntity"]["name"] asset_name = instance.data["asset"] - - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) if not asset_doc: raise AssertionError(( "Couldn't find Asset document with name \"{}\"" @@ -52,7 +49,7 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): self.subset_name_variant, task_name, asset_doc, - legacy_io.Session["AVALON_PROJECT"] + project_name ) instance_name = f"{asset_name}_{subset_name}" diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 77163651c4..78e63f7ef0 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_id, get_project class CollectHierarchyInstance(pyblish.api.ContextPlugin): @@ -61,27 +61,32 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): **instance.data["anatomyData"]) def create_hierarchy(self, instance): - parents = list() - hierarchy = list() - visual_hierarchy = [instance.context.data["assetEntity"]] + asset_doc = instance.context.data["assetEntity"] + project_doc = instance.context.data["projectEntity"] + project_name = project_doc["name"] + visual_hierarchy = [asset_doc] + current_doc = asset_doc while True: - visual_parent = legacy_io.find_one( - {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - ) - if visual_parent: - visual_hierarchy.append(visual_parent) - else: - visual_hierarchy.append( - instance.context.data["projectEntity"]) + visual_parent_id = current_doc["data"]["visualParent"] + visual_parent = None + if visual_parent_id: + visual_parent = get_asset_by_id(project_name, visual_parent_id) + + if not visual_parent: + visual_hierarchy.append(project_doc) break + visual_hierarchy.append(visual_parent) + current_doc = visual_parent # add current selection context hierarchy from standalonepublisher + parents = list() for entity in reversed(visual_hierarchy): parents.append({ "entity_type": entity["data"]["entityType"], "entity_name": entity["name"] }) + hierarchy = list() if self.shot_add_hierarchy: parent_template_patern = re.compile(r"\{([a-z]*?)\}") # fill the parents parts from presets @@ -129,9 +134,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): self.log.debug(f"Hierarchy: {hierarchy}") self.log.debug(f"parents: {parents}") + tasks_to_add = dict() if self.shot_add_tasks: - tasks_to_add = dict() - project_doc = legacy_io.find_one({"type": "project"}) project_tasks = project_doc["config"]["tasks"] for task_name, task_data in self.shot_add_tasks.items(): _task_data = deepcopy(task_data) @@ -150,9 +154,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): task_name, list(project_tasks.keys()))) - instance.data["tasks"] = tasks_to_add - else: - instance.data["tasks"] = dict() + instance.data["tasks"] = tasks_to_add # updating hierarchy data instance.data["anatomyData"].update({ diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py index 9d94bfdc91..82d7247b2b 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py @@ -4,7 +4,7 @@ import collections import pyblish.api from pprint import pformat -from openpype.pipeline import legacy_io +from openpype.client import get_assets class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): @@ -119,8 +119,9 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): def _asset_docs_by_parent_id(self, instance): # Query all assets for project and store them by parent's id to list + project_name = instance.context.data["projectEntity"]["name"] asset_docs_by_parent_id = collections.defaultdict(list) - for asset_doc in legacy_io.find({"type": "asset"}): + for asset_doc in get_assets(project_name): parent_id = asset_doc["data"]["visualParent"] asset_docs_by_parent_id[parent_id].append(asset_doc) return asset_docs_by_parent_id diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py index 4c761c7a4c..19ea1a4778 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py @@ -1,9 +1,7 @@ import pyblish.api -from openpype.pipeline import ( - PublishXmlValidationError, - legacy_io, -) +from openpype.client import get_assets +from openpype.pipeline import PublishXmlValidationError class ValidateTaskExistence(pyblish.api.ContextPlugin): @@ -20,15 +18,11 @@ class ValidateTaskExistence(pyblish.api.ContextPlugin): for instance in context: asset_names.add(instance.data["asset"]) - asset_docs = legacy_io.find( - { - "type": "asset", - "name": {"$in": list(asset_names)} - }, - { - "name": 1, - "data.tasks": 1 - } + project_name = context.data["projectEntity"]["name"] + asset_docs = get_assets( + project_name, + asset_names=asset_names, + fields=["name", "data.tasks"] ) tasks_by_asset_names = {} for asset_doc in asset_docs: From c01f65a9176413a388248986950162d21428da91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:02:34 +0200 Subject: [PATCH 140/258] removed unused import --- .../standalonepublisher/plugins/publish/collect_hierarchy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 78e63f7ef0..7922ca7f31 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.client import get_asset_by_id, get_project +from openpype.client import get_asset_by_id class CollectHierarchyInstance(pyblish.api.ContextPlugin): From 010ee05eff630531a77964c7bfe39460cee9421c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:26:46 +0200 Subject: [PATCH 141/258] use query functions in blender --- openpype/hosts/blender/api/pipeline.py | 7 ++-- .../blender/plugins/publish/extract_layout.py | 39 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 5b81764644..93d81145bc 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,6 +10,7 @@ from . import ops import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, legacy_io, @@ -83,11 +84,9 @@ def uninstall(): def set_start_end_frames(): + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) scene = bpy.context.scene diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8ecc78a2c6..2b3fa6a608 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -1,13 +1,11 @@ import os import json -from bson.objectid import ObjectId - import bpy import bpy_extras import bpy_extras.anim_utils -from openpype.pipeline import legacy_io +from openpype.client import get_representation_by_name from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY import openpype.api @@ -131,43 +129,32 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 + project_name = instance.context["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) - parent = metadata["parent"] + version_id = metadata["parent"] family = metadata["family"] - self.log.debug("Parent: {}".format(parent)) + self.log.debug("Parent: {}".format(version_id)) # Get blend reference - blend = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "blend" - }, - projection={"_id": True}) + blend = get_representation_by_name( + project_name, "blend", version_id, fields=["_id"] + ) blend_id = None if blend: blend_id = blend["_id"] # Get fbx reference - fbx = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "fbx" - }, - projection={"_id": True}) + fbx = get_representation_by_name( + project_name, "fbx", version_id, fields=["_id"] + ) fbx_id = None if fbx: fbx_id = fbx["_id"] # Get abc reference - abc = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "abc" - }, - projection={"_id": True}) + abc = get_representation_by_name( + project_name, "abc", version_id, fields=["_id"] + ) abc_id = None if abc: abc_id = abc["_id"] From 7c7c1486a401f2b260f013bf3c9dce0b3f123575 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:15:28 +0200 Subject: [PATCH 142/258] use own rest api endpoint instead of using private from avalon --- .../webserver_service/webpublish_routes.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 70324fc39c..fcf9f10ffe 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -17,14 +17,24 @@ from openpype.lib.remote_publish import ( REPROCESS_STATUS ) from openpype.pipeline import AvalonMongoDB -from openpype_modules.avalon_apps.rest_api import _RestApiEndpoint from openpype.settings import get_project_settings +from openpype_modules.webserver.base_routes import RestApiEndpoint log = PypeLogger.get_logger("WebServer") +class ResourceRestApiEndpoint(RestApiEndpoint): + def __init__(self, resource): + self.resource = resource + super(ResourceRestApiEndpoint, self).__init__() + + @property + def dbcon(self): + return self.resource.dbcon + + class RestApiResource: """Resource carrying needed info and Avalon DB connection for publish.""" def __init__(self, server_manager, executable, upload_dir, @@ -67,7 +77,7 @@ class OpenPypeRestApiResource(RestApiResource): self.dbcon = mongo_client[database_name]["webpublishes"] -class ProjectsEndpoint(_RestApiEndpoint): +class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] @@ -84,7 +94,7 @@ class ProjectsEndpoint(_RestApiEndpoint): ) -class HiearchyEndpoint(_RestApiEndpoint): +class HiearchyEndpoint(ResourceRestApiEndpoint): """Returns dictionary with context tree from assets.""" async def get(self, project_name) -> Response: query_projection = { @@ -183,7 +193,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(_RestApiEndpoint): +class BatchPublishEndpoint(ResourceRestApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -288,7 +298,7 @@ class BatchPublishEndpoint(_RestApiEndpoint): ) -class TaskPublishEndpoint(_RestApiEndpoint): +class TaskPublishEndpoint(ResourceRestApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -298,7 +308,7 @@ class TaskPublishEndpoint(_RestApiEndpoint): ) -class BatchStatusEndpoint(_RestApiEndpoint): +class BatchStatusEndpoint(ResourceRestApiEndpoint): """Returns dict with info for batch_id.""" async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -318,7 +328,7 @@ class BatchStatusEndpoint(_RestApiEndpoint): ) -class UserReportEndpoint(_RestApiEndpoint): +class UserReportEndpoint(ResourceRestApiEndpoint): """Returns list of dict with batch info for user (email address).""" async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, @@ -338,7 +348,7 @@ class UserReportEndpoint(_RestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(_RestApiEndpoint): +class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -378,7 +388,7 @@ class ConfiguredExtensionsEndpoint(_RestApiEndpoint): ) -class BatchReprocessEndpoint(_RestApiEndpoint): +class BatchReprocessEndpoint(ResourceRestApiEndpoint): """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, From 00c3554a5c1be7c07f48e6d018e275e2defd714f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:18:16 +0200 Subject: [PATCH 143/258] renamed 'OpenPypeRestApiResource' to 'WebpublishRestApiResource' --- .../webserver_service/webpublish_routes.py | 9 ++++----- .../webserver_service/webserver_cli.py | 15 +++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index fcf9f10ffe..d4aca1a797 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,9 +20,7 @@ from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint - - -log = PypeLogger.get_logger("WebServer") +log = PypeLogger.get_logger("WebpublishRoutes") class ResourceRestApiEndpoint(RestApiEndpoint): @@ -69,9 +67,10 @@ class RestApiResource: ).encode("utf-8") -class OpenPypeRestApiResource(RestApiResource): +class WebpublishRestApiResource: """Resource carrying OP DB connection for storing batch info into DB.""" - def __init__(self, ): + + def __init__(self): mongo_client = OpenPypeMongoConnection.get_mongo_client() database_name = os.environ["OPENPYPE_DATABASE_NAME"] self.dbcon = mongo_client[database_name]["webpublishes"] diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index 909ea38bc6..f18aac1168 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -10,7 +10,7 @@ from openpype.lib import PypeLogger from .webpublish_routes import ( RestApiResource, - OpenPypeRestApiResource, + WebpublishRestApiResource, HiearchyEndpoint, ProjectsEndpoint, ConfiguredExtensionsEndpoint, @@ -27,7 +27,7 @@ from openpype.lib.remote_publish import ( ) -log = PypeLogger().get_logger("webserver_gui") +log = PypeLogger.get_logger("webserver_gui") def run_webserver(*args, **kwargs): @@ -86,27 +86,26 @@ def run_webserver(*args, **kwargs): ) # reporting - openpype_resource = OpenPypeRestApiResource() - batch_status_endpoint = BatchStatusEndpoint(openpype_resource) + webpublish_resource = WebpublishRestApiResource() + batch_status_endpoint = BatchStatusEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/batch_status/{batch_id}", batch_status_endpoint.dispatch ) - user_status_endpoint = UserReportEndpoint(openpype_resource) + user_status_endpoint = UserReportEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/publishes/{user}", user_status_endpoint.dispatch ) - webpublisher_batch_reprocess_endpoint = \ - BatchReprocessEndpoint(openpype_resource) + batch_reprocess_endpoint = BatchReprocessEndpoint(webpublish_resource) server_manager.add_route( "POST", "/api/webpublish/reprocess/{batch_id}", - webpublisher_batch_reprocess_endpoint.dispatch + batch_reprocess_endpoint.dispatch ) server_manager.start_server() From 444b8aa67347121b44762120768fa6b84249eb3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:19:35 +0200 Subject: [PATCH 144/258] use query functions from client --- .../webserver_service/webpublish_routes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index d4aca1a797..cfbb0939b5 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -2,11 +2,15 @@ import os import json import datetime -from bson.objectid import ObjectId import collections -from aiohttp.web_response import Response import subprocess +from bson.objectid import ObjectId +from aiohttp.web_response import Response +from openpype.client import ( + get_projects, + get_assets, +) from openpype.lib import ( OpenPypeMongoConnection, PypeLogger, @@ -80,7 +84,7 @@ class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] - for project_doc in self.dbcon.projects(): + for project_doc in get_projects(): ret_val = { "id": project_doc["_id"], "name": project_doc["name"] @@ -105,10 +109,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - query_projection - ) + asset_docs = get_assets(project_name, field=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 840f1a431490f0dea02b8e5f9990cc8f5451e1f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:20:15 +0200 Subject: [PATCH 145/258] separated endpoints to those with dbcon and without --- .../webserver_service/webpublish_routes.py | 36 ++++++++++++------- .../webserver_service/webserver_cli.py | 6 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index cfbb0939b5..b1041bf6cb 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,7 +20,6 @@ from openpype.lib.remote_publish import ( ERROR_STATUS, REPROCESS_STATUS ) -from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint @@ -32,6 +31,8 @@ class ResourceRestApiEndpoint(RestApiEndpoint): self.resource = resource super(ResourceRestApiEndpoint, self).__init__() + +class WebpublishApiEndpoint(ResourceRestApiEndpoint): @property def dbcon(self): return self.resource.dbcon @@ -49,9 +50,6 @@ class RestApiResource: studio_task_queue = collections.deque().dequeu self.studio_task_queue = studio_task_queue - self.dbcon = AvalonMongoDB() - self.dbcon.install() - @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -193,7 +191,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(ResourceRestApiEndpoint): +class BatchPublishEndpoint(WebpublishApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -298,7 +296,7 @@ class BatchPublishEndpoint(ResourceRestApiEndpoint): ) -class TaskPublishEndpoint(ResourceRestApiEndpoint): +class TaskPublishEndpoint(WebpublishApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -308,8 +306,12 @@ class TaskPublishEndpoint(ResourceRestApiEndpoint): ) -class BatchStatusEndpoint(ResourceRestApiEndpoint): - """Returns dict with info for batch_id.""" +class BatchStatusEndpoint(WebpublishApiEndpoint): + """Returns dict with info for batch_id. + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -328,8 +330,12 @@ class BatchStatusEndpoint(ResourceRestApiEndpoint): ) -class UserReportEndpoint(ResourceRestApiEndpoint): - """Returns list of dict with batch info for user (email address).""" +class UserReportEndpoint(WebpublishApiEndpoint): + """Returns list of dict with batch info for user (email address). + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, projection={"log": False})) @@ -348,7 +354,7 @@ class UserReportEndpoint(ResourceRestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): +class ConfiguredExtensionsEndpoint(WebpublishApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -388,8 +394,12 @@ class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): ) -class BatchReprocessEndpoint(ResourceRestApiEndpoint): - """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" +class BatchReprocessEndpoint(WebpublishApiEndpoint): + """Marks latest 'batch_id' for reprocessing, returns 404 if not found. + + Uses 'WebpublishRestApiResource'. + """ + async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, "status": ERROR_STATUS}).sort("_id", -1) diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index f18aac1168..1ed8f22b2c 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -69,16 +69,14 @@ def run_webserver(*args, **kwargs): ) # triggers publish - webpublisher_task_publish_endpoint = \ - BatchPublishEndpoint(resource) + webpublisher_task_publish_endpoint = BatchPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/batch", webpublisher_task_publish_endpoint.dispatch ) - webpublisher_batch_publish_endpoint = \ - TaskPublishEndpoint(resource) + webpublisher_batch_publish_endpoint = TaskPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/task", From c177b60221aac07ecc304db832c8f2199c691d9f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Jun 2022 11:11:51 +0200 Subject: [PATCH 146/258] Fix - added OPENPYPE_MONGO to filter Submit job has filter of allowed environment values, it missed OPENPYPE_MONGO. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..39a2aa2761 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -128,7 +128,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_USERNAME", "OPENPYPE_RENDER_JOB", - "OPENPYPE_PUBLISH_JOB" + "OPENPYPE_PUBLISH_JOB", + "OPENPYPE_MONGO" ] # custom deadline attributes From fae33600f60555edab8d6187e4754eb946b1e8f4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Jun 2022 11:14:06 +0200 Subject: [PATCH 147/258] Nuke: adding empty representations to every instance also clearing obsolete code --- .../plugins/publish/precollect_instances.py | 8 ++--- .../plugins/publish/precollect_workfile.py | 36 ++++++++----------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 1a8fa3e6ad..8bf7280cea 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -151,15 +151,11 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): "resolutionWidth": resolution_width, "resolutionHeight": resolution_height, "pixelAspect": pixel_aspect, - "review": review + "review": review, + "representations": [] }) self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].extend(instances) self.log.debug("context: {}".format(context)) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py index a2d1c80628..7349a8f424 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py @@ -17,7 +17,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): label = "Pre-collect Workfile" hosts = ['nuke'] - def process(self, context): + def process(self, context): # sourcery skip: avoid-builtin-shadow root = nuke.root() current_file = os.path.normpath(nuke.root().name()) @@ -74,20 +74,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): } context.data.update(script_data) - # creating instance data - instance.data.update({ - "subset": subset, - "label": base_name, - "name": base_name, - "publish": root.knob('publish').value(), - "family": family, - "families": [family], - "representations": list() - }) - - # adding basic script data - instance.data.update(script_data) - # creating representation representation = { 'name': 'nk', @@ -96,12 +82,18 @@ class CollectWorkfile(pyblish.api.ContextPlugin): "stagingDir": staging_dir, } - instance.data["representations"].append(representation) + # creating instance data + instance.data.update({ + "subset": subset, + "label": base_name, + "name": base_name, + "publish": root.knob('publish').value(), + "family": family, + "families": [family], + "representations": [representation] + }) + + # adding basic script data + instance.data.update(script_data) self.log.info('Publishing script version') - - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].append(instance) From 6e2802cf9280fb00562b8adf25c7114a937783e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 12:28:56 +0200 Subject: [PATCH 148/258] fix project entity access --- openpype/hosts/blender/plugins/publish/extract_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 2b3fa6a608..75d9cf440d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -129,7 +129,7 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 - project_name = instance.context["projectEntity"]["name"] + project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) From 73fb899e905efa2f7cfe9a54dcf8f4ccd06e6d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 14 Jun 2022 13:46:45 +0200 Subject: [PATCH 149/258] :construction: wip on validation of parent relationships --- .../publish/validate_camera_contents.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 38cf65f006..eb93245f93 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -33,7 +33,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): invalid = [] cameras = cmds.ls(shapes, type='camera', long=True) if len(cameras) != 1: - cls.log.warning("Camera instance must have a single camera. " + cls.log.error("Camera instance must have a single camera. " "Found {0}: {1}".format(len(cameras), cameras)) invalid.extend(cameras) @@ -51,16 +51,32 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") if not cls.validate_shapes: - return + cls.log.info("not validating shapes in the content") + + for member in members: + parents = cmds.ls(member, long=True)[0].split("|")[1:-1] + cls.log.info(parents) + parents_long_named = [ + "|".join(parents[:i]) for i in range(1, 1 + len(parents)) + ] + cls.log.info(parents_long_named) + if cameras[0] in parents_long_named: + cls.log.error( + "{} is parented under camera {}".format(member, cameras[0])) + invalid.extend(member) + return invalid + # non-camera shapes valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) shapes = set(shapes) - set(valid_shapes) if shapes: shapes = list(shapes) - cls.log.warning("Camera instance should only contain camera " + cls.log.error("Camera instance should only contain camera " "shapes. Found: {0}".format(shapes)) invalid.extend(shapes) + + invalid = list(set(invalid)) return invalid From d4e78369f019a8ebc46dd3702d8efc676d4f4473 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:03:12 +0200 Subject: [PATCH 150/258] added new function to retrieve only archived assets --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 83 +++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 16b1dcf321..e3b4ef5132 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -5,6 +5,7 @@ from .entities import ( get_asset_by_id, get_asset_by_name, get_assets, + get_archived_assets, get_asset_ids_with_subsets, get_subset_by_id, @@ -41,6 +42,7 @@ __all__ = ( "get_asset_by_id", "get_asset_by_name", "get_assets", + "get_archived_assets", "get_asset_ids_with_subsets", "get_subset_by_id", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a56288c1e8..0827e12288 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -135,8 +135,16 @@ def get_asset_by_name(project_name, asset_name, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) -def get_assets( - project_name, asset_ids=None, asset_names=None, archived=False, fields=None +# NOTE this could be just public function? +# - any better variable name instead of 'standard'? +# - same approach can be used for rest of types +def _get_assets( + project_name, + asset_ids=None, + asset_names=None, + standard=True, + archived=False, + fields=None ): """Assets for specified project by passed filters. @@ -149,6 +157,8 @@ def get_assets( project_name (str): Name of project where to look for queried entities. asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. + standard (bool): Query standart assets (type 'asset'). + archived (bool): Query archived assets (type 'archived_asset'). fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -157,10 +167,15 @@ def get_assets( passed filters. """ - asset_types = ["asset"] + asset_types = [] + if standard: + asset_types.append("asset") if archived: asset_types.append("archived_asset") + if not asset_types: + return [] + if len(asset_types) == 1: query_filter = {"type": asset_types[0]} else: @@ -182,6 +197,68 @@ def get_assets( return conn.find(query_filter, _prepare_fields(fields)) +def get_assets( + project_name, + asset_ids=None, + asset_names=None, + archived=False, + fields=None +): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + archived (bool): Add also archived assets. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + + return _get_assets( + project_name, asset_ids, asset_names, True, archived, fields + ) + + +def get_archived_assets( + project_name, + asset_ids=None, + asset_names=None, + fields=None +): + """Archived assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all archived assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + + return _get_assets( + project_name, asset_ids, asset_names, False, True, fields + ) + + def get_asset_ids_with_subsets(project_name, asset_ids=None): """Find out which assets have existing subsets. From a57318823310729b281ced80390d8dad8f786fb5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:03:40 +0200 Subject: [PATCH 151/258] use query functions in sync to avalon event --- .../event_sync_to_avalon.py | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index b5f199b3e4..a4e791aaf0 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -12,6 +12,12 @@ from pymongo import UpdateOne import arrow import ftrack_api +from openpype.client import ( + get_project, + get_assets, + get_archived_assets, + get_asset_ids_with_subsets +) from openpype.pipeline import AvalonMongoDB, schema from openpype_modules.ftrack.lib import ( @@ -149,12 +155,11 @@ class SyncToAvalonEvent(BaseEvent): @property def avalon_entities(self): if self._avalon_ents is None: + project_name = self.cur_project["full_name"] self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] - ) - avalon_project = self.dbcon.find_one({"type": "project"}) - avalon_entities = list(self.dbcon.find({"type": "asset"})) + self.dbcon.Session["AVALON_PROJECT"] = project_name + avalon_project = get_project(project_name) + avalon_entities = list(get_assets(project_name)) self._avalon_ents = (avalon_project, avalon_entities) return self._avalon_ents @@ -284,28 +289,21 @@ class SyncToAvalonEvent(BaseEvent): self._avalon_ents_by_ftrack_id[ftrack_id] = doc @property - def avalon_subsets_by_parents(self): - if self._avalon_subsets_by_parents is None: - self._avalon_subsets_by_parents = collections.defaultdict(list) - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] + def avalon_asset_ids_with_subsets(self): + if self._avalon_asset_ids_with_subsets is None: + project_name = self.cur_project["full_name"] + self._avalon_asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name ) - for subset in self.dbcon.find({"type": "subset"}): - self._avalon_subsets_by_parents[subset["parent"]].append( - subset - ) - return self._avalon_subsets_by_parents + + return self._avalon_asset_ids_with_subsets @property def avalon_archived_by_id(self): if self._avalon_archived_by_id is None: self._avalon_archived_by_id = {} - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] - ) - for asset in self.dbcon.find({"type": "archived_asset"}): + project_name = self.cur_project["full_name"] + for asset in get_archived_assets(project_name): self._avalon_archived_by_id[asset["_id"]] = asset return self._avalon_archived_by_id @@ -327,7 +325,7 @@ class SyncToAvalonEvent(BaseEvent): avalon_project, avalon_entities = self.avalon_entities self._changeability_by_mongo_id[avalon_project["_id"]] = False self._bubble_changeability( - list(self.avalon_subsets_by_parents.keys()) + list(self.avalon_asset_ids_with_subsets) ) return self._changeability_by_mongo_id @@ -449,14 +447,9 @@ class SyncToAvalonEvent(BaseEvent): if not entity: # if entity is not found then it is subset without parent if entity_id in unchangeable_ids: - _subset_ids = [ - str(sub["_id"]) for sub in - self.avalon_subsets_by_parents[entity_id] - ] - joined_subset_ids = "| ".join(_subset_ids) self.log.warning(( - "Parent <{}> for subsets <{}> does not exist" - ).format(str(entity_id), joined_subset_ids)) + "Parent <{}> with subsets does not exist" + ).format(str(entity_id))) else: self.log.warning(( "In avalon are entities without valid parents that" @@ -483,7 +476,7 @@ class SyncToAvalonEvent(BaseEvent): self._avalon_ents_by_parent_id = None self._avalon_ents_by_ftrack_id = None self._avalon_ents_by_name = None - self._avalon_subsets_by_parents = None + self._avalon_asset_ids_with_subsets = None self._changeability_by_mongo_id = None self._avalon_archived_by_id = None self._avalon_archived_by_name = None From 177be6880f7bcf1de63a39ed07cd26a742345236 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:34:31 +0200 Subject: [PATCH 152/258] added versions argument to get_versions --- openpype/client/entities.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 0827e12288..6e1f4c556b 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -505,6 +505,7 @@ def _get_versions( project_name, subset_ids=None, version_ids=None, + versions=None, standard=True, hero=False, fields=None @@ -535,6 +536,16 @@ def _get_versions( return [] query_filter["_id"] = {"$in": version_ids} + if versions is not None: + versions = list(versions) + if not versions: + return [] + + if len(versions) == 1: + query_filter["name"] = versions[0] + else: + query_filter["name"] = {"$in": versions} + conn = _get_project_connection(project_name) return conn.find(query_filter, _prepare_fields(fields)) @@ -544,6 +555,7 @@ def get_versions( project_name, version_ids=None, subset_ids=None, + versions=None, hero=False, fields=None ): @@ -557,6 +569,8 @@ def get_versions( Filter ignored if 'None' is passed. subset_ids (list[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. + versions (list[int]): Version names (as integers). + Filter ignored if 'None' is passed. hero (bool): Look also for hero versions. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -569,6 +583,7 @@ def get_versions( project_name, subset_ids, version_ids, + versions, standard=True, hero=hero, fields=fields From e6ddd6797d0ee9689d57b8bfd1e585dc55babffc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 15:30:29 +0200 Subject: [PATCH 153/258] use query functions in prepare project --- .../action_prepare_project.py | 14 ++++---------- .../event_handlers_user/action_prepare_project.py | 14 ++++---------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 975e49cb28..361aa98d16 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -1,8 +1,8 @@ import json +from openpype.client import get_project from openpype.api import ProjectSettings from openpype.lib import create_project -from openpype.pipeline import AvalonMongoDB from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( @@ -363,12 +363,8 @@ class PrepareProjectServer(ServerAction): project_name = project_entity["full_name"] # Try to find project document - dbcon = AvalonMongoDB() - dbcon.install() - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({ - "type": "project" - }) + project_doc = get_project(project_name) + # Create project if is not available # - creation is required to be able set project anatomy and attributes if not project_doc: @@ -376,9 +372,7 @@ class PrepareProjectServer(ServerAction): self.log.info("Creating project \"{} [{}]\"".format( project_name, project_code )) - create_project(project_name, project_code, dbcon=dbcon) - - dbcon.uninstall() + create_project(project_name, project_code) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 0b14e7aa2b..e9dc11de9f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,8 +1,8 @@ import json +from openpype.client import get_project from openpype.api import ProjectSettings from openpype.lib import create_project -from openpype.pipeline import AvalonMongoDB from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( @@ -389,12 +389,8 @@ class PrepareProjectLocal(BaseAction): project_name = project_entity["full_name"] # Try to find project document - dbcon = AvalonMongoDB() - dbcon.install() - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({ - "type": "project" - }) + project_doc = get_project(project_name) + # Create project if is not available # - creation is required to be able set project anatomy and attributes if not project_doc: @@ -402,9 +398,7 @@ class PrepareProjectLocal(BaseAction): self.log.info("Creating project \"{} [{}]\"".format( project_name, project_code )) - create_project(project_name, project_code, dbcon=dbcon) - - dbcon.uninstall() + create_project(project_name, project_code) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] From 77d496d88bc1a4c7e0aea436512d695ee72c92bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:05:51 +0200 Subject: [PATCH 154/258] use query in applications action --- .../ftrack/event_handlers_user/action_applications.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_applications.py b/openpype/modules/ftrack/event_handlers_user/action_applications.py index b25bc1b5cb..102f04c956 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_applications.py +++ b/openpype/modules/ftrack/event_handlers_user/action_applications.py @@ -1,5 +1,6 @@ import os +from openpype.client import get_project from openpype_modules.ftrack.lib import BaseAction from openpype.lib.applications import ( ApplicationManager, @@ -7,7 +8,6 @@ from openpype.lib.applications import ( ApplictionExecutableNotFound, CUSTOM_LAUNCH_APP_GROUPS ) -from openpype.pipeline import AvalonMongoDB class AppplicationsAction(BaseAction): @@ -25,7 +25,6 @@ class AppplicationsAction(BaseAction): super(AppplicationsAction, self).__init__(*args, **kwargs) self.application_manager = ApplicationManager() - self.dbcon = AvalonMongoDB() @property def discover_identifier(self): @@ -110,12 +109,7 @@ class AppplicationsAction(BaseAction): if avalon_project_doc is None: ft_project = self.get_project_from_entity(entity) project_name = ft_project["full_name"] - if not self.dbcon.is_installed(): - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = project_name - avalon_project_doc = self.dbcon.find_one({ - "type": "project" - }) or False + avalon_project_doc = get_project(project_name) or False event["data"]["avalon_project_doc"] = avalon_project_doc if not avalon_project_doc: From 7945ca0011c54eaed02655c64481c8dc2dd6bd6d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:06:12 +0200 Subject: [PATCH 155/258] use query functions in delete old versions action --- .../action_delete_old_versions.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index a0bf6622e9..3400c509ab 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -5,7 +5,12 @@ import uuid import clique from pymongo import UpdateOne - +from openpype.client import ( + get_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import Anatomy from openpype.lib import StringTemplate, TemplateUnsolved from openpype.pipeline import AvalonMongoDB @@ -198,10 +203,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Project is set to {}".format(project_name)) # Get Assets from avalon database - assets = list(self.dbcon.find({ - "type": "asset", - "name": {"$in": avalon_asset_names} - })) + assets = list( + get_assets(project_name, asset_names=avalon_asset_names) + ) asset_id_to_name_map = { asset["_id"]: asset["name"] for asset in assets } @@ -210,10 +214,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Collected assets ({})".format(len(asset_ids))) # Get Subsets - subsets = list(self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_ids} - })) + subsets = list( + get_subsets(project_name, asset_ids=asset_ids) + ) subsets_by_id = {} subset_ids = [] for subset in subsets: @@ -230,10 +233,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Collected subsets ({})".format(len(subset_ids))) # Get Versions - versions = list(self.dbcon.find({ - "type": "version", - "parent": {"$in": subset_ids} - })) + versions = list( + get_versions(project_name, subset_ids=subset_ids) + ) versions_by_parent = collections.defaultdict(list) for ent in versions: @@ -295,10 +297,9 @@ class DeleteOldVersions(BaseAction): "message": msg } - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list( + get_representations(project_name, version_ids=version_ids) + ) self.log.debug( "Collected representations to remove ({})".format(len(repres)) From af0b97fafe625ad1094627435c22e33e5881a2d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:07:47 +0200 Subject: [PATCH 156/258] use query functions in delivery action --- .../event_handlers_user/action_delivery.py | 152 ++++++++---------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 86d88ef7cc..47f2853820 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -3,8 +3,13 @@ import copy import json import collections -from bson.objectid import ObjectId - +from openpype.client import ( + get_project, + get_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import Anatomy, config from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY @@ -18,11 +23,9 @@ from openpype.lib.delivery import ( process_single_file, process_sequence ) -from openpype.pipeline import AvalonMongoDB class Delivery(BaseAction): - identifier = "delivery.action" label = "Delivery" description = "Deliver data to client" @@ -30,11 +33,6 @@ class Delivery(BaseAction): icon = statics_icon("ftrack", "action_icons", "Delivery.svg") settings_key = "delivery_action" - def __init__(self, *args, **kwargs): - self.dbcon = AvalonMongoDB() - - super(Delivery, self).__init__(*args, **kwargs) - def discover(self, session, entities, event): is_valid = False for entity in entities: @@ -57,9 +55,7 @@ class Delivery(BaseAction): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = self.dbcon.find_one({"type": "project"}, {"name": True}) + project_doc = get_project(project_name, fields=["name"]) if not project_doc: return { "success": False, @@ -68,8 +64,7 @@ class Delivery(BaseAction): ).format(project_name) } - repre_names = self._get_repre_names(session, entities) - self.dbcon.uninstall() + repre_names = self._get_repre_names(project_name, session, entities) items.append({ "type": "hidden", @@ -198,17 +193,21 @@ class Delivery(BaseAction): "title": title } - def _get_repre_names(self, session, entities): - version_ids = self._get_interest_version_ids(session, entities) + def _get_repre_names(self, project_name, session, entities): + version_ids = self._get_interest_version_ids( + project_name, session, entities + ) if not version_ids: return [] - repre_docs = self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) - return list(sorted(repre_docs.distinct("name"))) + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name"] + ) + repre_names = {repre_doc["name"] for repre_doc in repre_docs} + return list(sorted(repre_names)) - def _get_interest_version_ids(self, session, entities): + def _get_interest_version_ids(self, project_name, session, entities): # Extract AssetVersion entities asset_versions = self._extract_asset_versions(session, entities) # Prepare Asset ids @@ -235,14 +234,18 @@ class Delivery(BaseAction): subset_names.add(asset["name"]) version_nums.add(asset_version["version"]) - asset_docs_by_ftrack_id = self._get_asset_docs(session, parent_ids) + asset_docs_by_ftrack_id = self._get_asset_docs( + project_name, session, parent_ids + ) subset_docs = self._get_subset_docs( + project_name, asset_docs_by_ftrack_id, subset_names, asset_versions, assets_by_id ) version_docs = self._get_version_docs( + project_name, asset_docs_by_ftrack_id, subset_docs, version_nums, @@ -290,6 +293,7 @@ class Delivery(BaseAction): def _get_version_docs( self, + project_name, asset_docs_by_ftrack_id, subset_docs, version_nums, @@ -300,11 +304,11 @@ class Delivery(BaseAction): subset_doc["_id"]: subset_doc for subset_doc in subset_docs } - version_docs = list(self.dbcon.find({ - "type": "version", - "parent": {"$in": list(subset_docs_by_id.keys())}, - "name": {"$in": list(version_nums)} - })) + version_docs = list(get_versions( + project_name, + subset_ids=subset_docs_by_id.keys(), + versions=version_nums + )) version_docs_by_parent_id = collections.defaultdict(dict) for version_doc in version_docs: subset_doc = subset_docs_by_id[version_doc["parent"]] @@ -345,6 +349,7 @@ class Delivery(BaseAction): def _get_subset_docs( self, + project_name, asset_docs_by_ftrack_id, subset_names, asset_versions, @@ -354,11 +359,11 @@ class Delivery(BaseAction): asset_doc["_id"] for asset_doc in asset_docs_by_ftrack_id.values() ] - subset_docs = list(self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_doc_ids}, - "name": {"$in": list(subset_names)} - })) + subset_docs = list(get_subsets( + project_name, + asset_ids=asset_doc_ids, + subset_names=subset_names + )) subset_docs_by_parent_id = collections.defaultdict(dict) for subset_doc in subset_docs: asset_id = subset_doc["parent"] @@ -385,15 +390,21 @@ class Delivery(BaseAction): filtered_subsets.append(subset_doc) return filtered_subsets - def _get_asset_docs(self, session, parent_ids): - asset_docs = list(self.dbcon.find({ - "type": "asset", - "data.ftrackId": {"$in": list(parent_ids)} - })) + def _get_asset_docs(self, project_name, session, parent_ids): + asset_docs = list(get_assets( + project_name, fields=["_id", "name", "data.ftrackId"] + )) + asset_docs_by_id = {} + asset_docs_by_name = {} asset_docs_by_ftrack_id = {} for asset_doc in asset_docs: + asset_id = str(asset_doc["_id"]) + asset_name = asset_doc["name"] ftrack_id = asset_doc["data"].get("ftrackId") + + asset_docs_by_id[asset_id] = asset_doc + asset_docs_by_name[asset_name] = asset_doc if ftrack_id: asset_docs_by_ftrack_id[ftrack_id] = asset_doc @@ -406,15 +417,15 @@ class Delivery(BaseAction): avalon_mongo_id_values = query_custom_attributes( session, [attr_def["id"]], parent_ids, True ) - entity_ids_by_mongo_id = { - ObjectId(item["value"]): item["entity_id"] - for item in avalon_mongo_id_values - if item["value"] - } - missing_ids = set(parent_ids) - for entity_id in set(entity_ids_by_mongo_id.values()): - if entity_id in missing_ids: + for item in avalon_mongo_id_values: + if not item["value"]: + continue + asset_id = item["value"] + entity_id = item["entity_id"] + asset_doc = asset_docs_by_id.get(asset_id) + if asset_doc: + asset_docs_by_ftrack_id[entity_id] = asset_doc missing_ids.remove(entity_id) entity_ids_by_name = {} @@ -427,36 +438,10 @@ class Delivery(BaseAction): for entity in not_found_entities } - expressions = [] - if entity_ids_by_mongo_id: - expression = { - "type": "asset", - "_id": {"$in": list(entity_ids_by_mongo_id.keys())} - } - expressions.append(expression) - - if entity_ids_by_name: - expression = { - "type": "asset", - "name": {"$in": list(entity_ids_by_name.keys())} - } - expressions.append(expression) - - if expressions: - if len(expressions) == 1: - filter = expressions[0] - else: - filter = {"$or": expressions} - - asset_docs = self.dbcon.find(filter) - for asset_doc in asset_docs: - if asset_doc["_id"] in entity_ids_by_mongo_id: - entity_id = entity_ids_by_mongo_id[asset_doc["_id"]] - asset_docs_by_ftrack_id[entity_id] = asset_doc - - elif asset_doc["name"] in entity_ids_by_name: - entity_id = entity_ids_by_name[asset_doc["name"]] - asset_docs_by_ftrack_id[entity_id] = asset_doc + for asset_name, entity_id in entity_ids_by_name.items(): + asset_doc = asset_docs_by_name.get(asset_name) + if asset_doc: + asset_docs_by_ftrack_id[entity_id] = asset_doc return asset_docs_by_ftrack_id @@ -490,7 +475,6 @@ class Delivery(BaseAction): session.commit() try: - self.dbcon.install() report = self.real_launch(session, entities, event) except Exception as exc: @@ -516,7 +500,6 @@ class Delivery(BaseAction): else: job["status"] = "failed" session.commit() - self.dbcon.uninstall() if not report["success"]: self.show_interface( @@ -558,16 +541,13 @@ class Delivery(BaseAction): if not os.path.exists(location_path): os.makedirs(location_path) - self.dbcon.Session["AVALON_PROJECT"] = project_name - self.log.debug("Collecting representations to process.") version_ids = self._get_interest_version_ids(session, entities) - repres_to_deliver = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids}, - "name": {"$in": repre_names} - })) - + repres_to_deliver = list(get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids + )) anatomy = Anatomy(project_name) format_dict = get_format_dict(anatomy, location_path) From ac48f50fdffb2e1650ce3681679b3ad822b3889c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:07:58 +0200 Subject: [PATCH 157/258] use query action in fill workfile attribute --- .../event_handlers_user/action_fill_workfile_attr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py index c7237a1150..d30c41a749 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py +++ b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py @@ -7,6 +7,10 @@ import datetime import ftrack_api +from openpype.client import ( + get_project, + get_assets, +) from openpype.api import get_project_settings from openpype.lib import ( get_workfile_template_key, @@ -14,7 +18,6 @@ from openpype.lib import ( Anatomy, StringTemplate, ) -from openpype.pipeline import AvalonMongoDB from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks @@ -248,10 +251,8 @@ class FillWorkfileAttributeAction(BaseAction): # Find matchin asset documents and map them by ftrack task entities # - result stored to 'asset_docs_with_task_entities' is list with # tuple `(asset document, [task entitis, ...])` - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name # Quety all asset documents - asset_docs = list(dbcon.find({"type": "asset"})) + asset_docs = list(get_assets(project_name)) job_entity["data"] = json.dumps({ "description": "(1/3) Asset documents queried." }) @@ -276,7 +277,7 @@ class FillWorkfileAttributeAction(BaseAction): # Keep placeholders in the template unfilled host_name = "{app}" extension = "{ext}" - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(project_name) project_settings = get_project_settings(project_name) anatomy = Anatomy(project_name) templates_by_key = {} From dd07bbe9b248cf0096e318c25fa73365217222fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:31:32 +0200 Subject: [PATCH 158/258] use query functions in event user assiment --- .../event_user_assigment.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py index 593fc5e596..82b79e986b 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py +++ b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py @@ -1,11 +1,9 @@ import re import subprocess +from openpype.client import get_asset_by_id, get_asset_by_name from openpype_modules.ftrack.lib import BaseEvent from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from openpype.pipeline import AvalonMongoDB - -from bson.objectid import ObjectId from openpype.api import Anatomy, get_project_settings @@ -36,8 +34,6 @@ class UserAssigmentEvent(BaseEvent): 3) path to publish files of task user was (de)assigned to """ - db_con = AvalonMongoDB() - def error(self, *err): for e in err: self.log.error(e) @@ -101,26 +97,16 @@ class UserAssigmentEvent(BaseEvent): :rtype: dict """ parent = task['parent'] - self.db_con.install() - self.db_con.Session['AVALON_PROJECT'] = task['project']['full_name'] - + project_name = task["project"]["full_name"] avalon_entity = None parent_id = parent['custom_attributes'].get(CUST_ATTR_ID_KEY) if parent_id: - parent_id = ObjectId(parent_id) - avalon_entity = self.db_con.find_one({ - '_id': parent_id, - 'type': 'asset' - }) + avalon_entity = get_asset_by_id(project_name, parent_id) if not avalon_entity: - avalon_entity = self.db_con.find_one({ - 'type': 'asset', - 'name': parent['name'] - }) + avalon_entity = get_asset_by_name(project_name, parent["name"]) if not avalon_entity: - self.db_con.uninstall() msg = 'Entity "{}" not found in avalon database'.format( parent['name'] ) @@ -129,7 +115,6 @@ class UserAssigmentEvent(BaseEvent): 'success': False, 'message': msg } - self.db_con.uninstall() return avalon_entity def _get_hierarchy(self, asset): From 951a23484e98c1831063c81306ed9f298f957ce5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:31:51 +0200 Subject: [PATCH 159/258] use query functions in RV action --- .../ftrack/event_handlers_user/action_rv.py | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py index 040ca75582..2480ea7f95 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -5,9 +5,16 @@ import json import ftrack_api +from openpype.client import ( + get_asset_by_name, + get_subset_by_name, + get_version_by_name, + get_representation_by_name +) +from openpype.api import Anatomy from openpype.pipeline import ( get_representation_path, - legacy_io, + AvalonMongoDB, ) from openpype_modules.ftrack.lib import BaseAction, statics_icon @@ -255,9 +262,10 @@ class RVAction(BaseAction): "Component", list(event["data"]["values"].values())[0] )["version"]["asset"]["parent"]["link"][0] project = session.get(link["type"], link["id"]) - os.environ["AVALON_PROJECT"] = project["name"] - legacy_io.Session["AVALON_PROJECT"] = project["name"] - legacy_io.install() + project_name = project["full_name"] + dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + anatomy = Anatomy(project_name) location = ftrack_api.Session().pick_location() @@ -281,37 +289,38 @@ class RVAction(BaseAction): if online_source: continue - asset = legacy_io.find_one({"type": "asset", "name": parent_name}) - subset = legacy_io.find_one( - { - "type": "subset", - "name": component["version"]["asset"]["name"], - "parent": asset["_id"] - } + subset_name = component["version"]["asset"]["name"] + version_name = component["version"]["version"] + representation_name = component["file_type"][1:] + + asset_doc = get_asset_by_name( + project_name, parent_name, fields=["_id"] ) - version = legacy_io.find_one( - { - "type": "version", - "name": component["version"]["version"], - "parent": subset["_id"] - } + subset_doc = get_subset_by_name( + project_name, + subset_name=subset_name, + asset_id=asset_doc["_id"] ) - representation = legacy_io.find_one( - { - "type": "representation", - "parent": version["_id"], - "name": component["file_type"][1:] - } + version_doc = get_version_by_name( + project_name, + version=version_name, + subset_id=subset_doc["_id"] ) - if representation is None: - representation = legacy_io.find_one( - { - "type": "representation", - "parent": version["_id"], - "name": "preview" - } + repre_doc = get_representation_by_name( + project_name, + version_id=version_doc["_id"], + representation_name=representation_name + ) + if not repre_doc: + repre_doc = get_representation_by_name( + project_name, + version_id=version_doc["_id"], + representation_name="preview" ) - paths.append(get_representation_path(representation)) + + paths.append(get_representation_path( + repre_doc, root=anatomy.roots, dbcon=dbcon + )) return paths From 38f058fca0ffaeef4cbe888433e3593fb282b048 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:32:34 +0200 Subject: [PATCH 160/258] use query functions in delete asset action --- .../action_delete_asset.py | 97 +++++++++---------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index ee5c3d0d97..b5b4ec5ac5 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -4,6 +4,7 @@ from datetime import datetime from bson.objectid import ObjectId +from openpype.client import get_assets, get_subsets from openpype.pipeline import AvalonMongoDB from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks @@ -91,11 +92,9 @@ class DeleteAssetSubset(BaseAction): continue ftrack_id = entity.get("entityId") - if not ftrack_id: - continue - - ftrack_ids.append(ftrack_id) - + if ftrack_id: + ftrack_ids.append(ftrack_id) + if project_in_selection: msg = "It is not possible to use this action on project entity." self.show_message(event, msg, True) @@ -120,48 +119,49 @@ class DeleteAssetSubset(BaseAction): "message": "Invalid selection for this action (Bug)" } - if entities[0].entity_type.lower() == "project": - project = entities[0] - else: - project = entities[0]["project"] - + project = self.get_project_from_entity(entities[0], session) project_name = project["full_name"] self.dbcon.Session["AVALON_PROJECT"] = project_name - selected_av_entities = list(self.dbcon.find({ - "type": "asset", - "data.ftrackId": {"$in": ftrack_ids} - })) + asset_docs = list(get_assets( + project_name, + fields=["_id", "name", "data.ftrackId", "data.parents"] + )) + selected_av_entities = [] + found_ftrack_ids = set() + asset_docs_by_name = collections.defaultdict(list) + for asset_doc in asset_docs: + ftrack_id = asset_doc["data"].get("ftrackId") + if ftrack_id: + found_ftrack_ids.add(ftrack_id) + selected_av_entities.append(asset_doc) + + asset_name = asset_doc["name"] + asset_docs_by_name[asset_name].append(asset_doc) + found_without_ftrack_id = {} - if len(selected_av_entities) != len(ftrack_ids): - found_ftrack_ids = [ - ent["data"]["ftrackId"] for ent in selected_av_entities - ] - for ftrack_id, entity in entity_mapping.items(): - if ftrack_id in found_ftrack_ids: + for ftrack_id, entity in entity_mapping.items(): + if ftrack_id in found_ftrack_ids: + continue + + av_ents_by_name = asset_docs_by_name[entity["name"]] + if not av_ents_by_name: + continue + + ent_path_items = [ent["name"] for ent in entity["link"]] + parents = ent_path_items[1:len(ent_path_items)-1:] + # TODO we should say to user that + # few of them are missing in avalon + for av_ent in av_ents_by_name: + if av_ent["data"]["parents"] != parents: continue - av_ents_by_name = list(self.dbcon.find({ - "type": "asset", - "name": entity["name"] - })) - if not av_ents_by_name: - continue - - ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] - # TODO we should say to user that - # few of them are missing in avalon - for av_ent in av_ents_by_name: - if av_ent["data"]["parents"] != parents: - continue - - # TODO we should say to user that found entity - # with same name does not match same ftrack id? - if "ftrackId" not in av_ent["data"]: - selected_av_entities.append(av_ent) - found_without_ftrack_id[str(av_ent["_id"])] = ftrack_id - break + # TODO we should say to user that found entity + # with same name does not match same ftrack id? + if "ftrackId" not in av_ent["data"]: + selected_av_entities.append(av_ent) + found_without_ftrack_id[str(av_ent["_id"])] = ftrack_id + break if not selected_av_entities: return { @@ -206,10 +206,7 @@ class DeleteAssetSubset(BaseAction): items.append(id_item) asset_ids = [ent["_id"] for ent in selected_av_entities] - subsets_for_selection = self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_ids} - }) + subsets_for_selection = get_subsets(project_name, asset_ids=asset_ids) asset_ending = "" if len(selected_av_entities) > 1: @@ -459,13 +456,9 @@ class DeleteAssetSubset(BaseAction): if len(assets_to_delete) > 0: map_av_ftrack_id = spec_data["without_ftrack_id"] # Prepare data when deleting whole avalon asset - avalon_assets = self.dbcon.find( - {"type": "asset"}, - { - "_id": 1, - "data.visualParent": 1, - "data.ftrackId": 1 - } + avalon_assets = get_assets( + project_name, + fields=["_id", "data.visualParent", "data.ftrackId"] ) avalon_assets_by_parent = collections.defaultdict(list) for asset in avalon_assets: From 46e22241bd8d7d61b427f9d0f4fff930cc7549a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:32:55 +0200 Subject: [PATCH 161/258] use query functions in store thumbnails to avalon --- .../action_store_thumbnails_to_avalon.py | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py index 62fdfa2bdd..d655dddcaf 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py @@ -5,6 +5,14 @@ import requests from bson.objectid import ObjectId +from openpype.client import ( + get_project, + get_asset_by_id, + get_assets, + get_subset_by_name, + get_version_by_name, + get_representations +) from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype.api import Anatomy from openpype.pipeline import AvalonMongoDB @@ -385,7 +393,7 @@ class StoreThumbnailsToAvalon(BaseAction): db_con.Session["AVALON_PROJECT"] = project_name - avalon_project = db_con.find_one({"type": "project"}) + avalon_project = get_project(project_name) output["project"] = avalon_project if not avalon_project: @@ -399,19 +407,17 @@ class StoreThumbnailsToAvalon(BaseAction): asset_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) if asset_mongo_id: try: - asset_mongo_id = ObjectId(asset_mongo_id) - asset_ent = db_con.find_one({ - "type": "asset", - "_id": asset_mongo_id - }) + asset_ent = get_asset_by_id(project_name, asset_mongo_id) except Exception: pass if not asset_ent: - asset_ent = db_con.find_one({ - "type": "asset", - "data.ftrackId": parent["id"] - }) + asset_docs = get_assets(project_name, asset_names=[parent["name"]]) + for asset_doc in asset_docs: + ftrack_id = asset_doc.get("data", {}).get("ftrackId") + if ftrack_id == parent["id"]: + asset_ent = asset_doc + break output["asset"] = asset_ent @@ -422,13 +428,11 @@ class StoreThumbnailsToAvalon(BaseAction): ) return output - asset_mongo_id = asset_ent["_id"] - - subset_ent = db_con.find_one({ - "type": "subset", - "parent": asset_mongo_id, - "name": subset_name - }) + subset_ent = get_subset_by_name( + project_name, + subset_name=subset_name, + asset_id=asset_ent["_id"] + ) output["subset"] = subset_ent @@ -439,11 +443,11 @@ class StoreThumbnailsToAvalon(BaseAction): ).format(subset_name, ent_path) return output - version_ent = db_con.find_one({ - "type": "version", - "name": version, - "parent": subset_ent["_id"] - }) + version_ent = get_version_by_name( + project_name, + version, + subset_ent["_id"] + ) output["version"] = version_ent @@ -454,10 +458,10 @@ class StoreThumbnailsToAvalon(BaseAction): ).format(version, subset_name, ent_path) return output - repre_ents = list(db_con.find({ - "type": "representation", - "parent": version_ent["_id"] - })) + repre_ents = list(get_representations( + project_name, + version_ids=[version_ent["_id"]] + )) output["representations"] = repre_ents return output From be2238718b402b2428335b61643cf0bab2165776 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:46:14 +0200 Subject: [PATCH 162/258] use query functions in avalon sync --- openpype/modules/ftrack/lib/avalon_sync.py | 70 +++++++++++----------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index e4ba651bfd..68b5c62c53 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -6,6 +6,14 @@ import numbers import six +from openpype.client import ( + get_project, + get_assets, + get_archived_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import ( Logger, get_anatomy_settings @@ -576,6 +584,10 @@ class SyncEntitiesFactory: self.ft_project_id = ft_project_id self.entities_dict = entities_dict + @property + def project_name(self): + return self.entities_dict[self.ft_project_id]["name"] + @property def avalon_ents_by_id(self): """ @@ -660,9 +672,9 @@ class SyncEntitiesFactory: (list) of assets """ if self._avalon_archived_ents is None: - self._avalon_archived_ents = [ - ent for ent in self.dbcon.find({"type": "archived_asset"}) - ] + self._avalon_archived_ents = list( + get_archived_assets(self.project_name) + ) return self._avalon_archived_ents @property @@ -730,7 +742,7 @@ class SyncEntitiesFactory: """ if self._subsets_by_parent_id is None: self._subsets_by_parent_id = collections.defaultdict(list) - for subset in self.dbcon.find({"type": "subset"}): + for subset in get_subsets(self.project_name): self._subsets_by_parent_id[str(subset["parent"])].append( subset ) @@ -1421,8 +1433,8 @@ class SyncEntitiesFactory: # Avalon entities self.dbcon.install() self.dbcon.Session["AVALON_PROJECT"] = ft_project_name - avalon_project = self.dbcon.find_one({"type": "project"}) - avalon_entities = self.dbcon.find({"type": "asset"}) + avalon_project = get_project(ft_project_name) + avalon_entities = get_assets(ft_project_name) self.avalon_project = avalon_project self.avalon_entities = avalon_entities @@ -2258,46 +2270,37 @@ class SyncEntitiesFactory: self._delete_subsets_without_asset(subsets_to_remove) def _delete_subsets_without_asset(self, not_existing_parents): - subset_ids = [] - version_ids = [] repre_ids = [] to_delete = [] + subset_ids = [] for parent_id in not_existing_parents: subsets = self.subsets_by_parent_id.get(parent_id) if not subsets: continue for subset in subsets: - if subset.get("type") != "subset": - continue - subset_ids.append(subset["_id"]) + if subset.get("type") == "subset": + subset_ids.append(subset["_id"]) - db_subsets = self.dbcon.find({ - "_id": {"$in": subset_ids}, - "type": "subset" - }) - if not db_subsets: - return - - db_versions = self.dbcon.find({ - "parent": {"$in": subset_ids}, - "type": "version" - }) - if db_versions: - version_ids = [ver["_id"] for ver in db_versions] - - db_repres = self.dbcon.find({ - "parent": {"$in": version_ids}, - "type": "representation" - }) - if db_repres: - repre_ids = [repre["_id"] for repre in db_repres] + db_versions = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id"] + ) + version_ids = [ver["_id"] for ver in db_versions] + db_repres = get_representations( + self.project_name, + version_ids=version_ids, + fields=["_id"] + ) + repre_ids = [repre["_id"] for repre in db_repres] to_delete.extend(subset_ids) to_delete.extend(version_ids) to_delete.extend(repre_ids) - self.dbcon.delete_many({"_id": {"$in": to_delete}}) + if to_delete: + self.dbcon.delete_many({"_id": {"$in": to_delete}}) # Probably deprecated def _check_changeability(self, parent_id=None): @@ -2779,8 +2782,7 @@ class SyncEntitiesFactory: def report(self): items = [] - project_name = self.entities_dict[self.ft_project_id]["name"] - title = "Synchronization report ({}):".format(project_name) + title = "Synchronization report ({}):".format(self.project_name) keys = ["error", "warning", "info"] for key in keys: From ce63c7c96dd5b3c9ab28d766e87390f7c5334eb2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:46:27 +0200 Subject: [PATCH 163/258] use query functions in integrate ftrack hierarchy --- .../publish/integrate_hierarchy_ftrack.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 73398941eb..1a5d74bf26 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -3,7 +3,8 @@ import collections import six import pyblish.api from copy import deepcopy -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_id + # Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" @@ -82,9 +83,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): auto_sync_state = project[ "custom_attributes"][CUST_ATTR_AUTO_SYNC] - if not legacy_io.Session: - legacy_io.install() - self.ft_project = None # disable termporarily ftrack project's autosyncing @@ -93,14 +91,14 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): try: # import ftrack hierarchy - self.import_to_ftrack(hierarchy_context) + self.import_to_ftrack(project_name, hierarchy_context) except Exception: raise finally: if auto_sync_state: self.auto_sync_on(project) - def import_to_ftrack(self, input_data, parent=None): + def import_to_ftrack(self, project_name, input_data, parent=None): # Prequery hiearchical custom attributes hier_custom_attributes = get_pype_attr(self.session)[1] hier_attr_by_key = { @@ -222,7 +220,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Incoming links. - self.create_links(entity_data, entity) + self.create_links(project_name, entity_data, entity) try: self.session.commit() except Exception: @@ -255,9 +253,9 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # Import children. if 'childs' in entity_data: self.import_to_ftrack( - entity_data['childs'], entity) + project_name, entity_data['childs'], entity) - def create_links(self, entity_data, entity): + def create_links(self, project_name, entity_data, entity): # Clear existing links. for link in entity.get("incoming_links", []): self.session.delete(link) @@ -270,9 +268,15 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Create new links. - for input in entity_data.get("inputs", []): - input_id = legacy_io.find_one({"_id": input})["data"]["ftrackId"] - assetbuild = self.session.get("AssetBuild", input_id) + for asset_id in entity_data.get("inputs", []): + asset_doc = get_asset_by_id(project_name, asset_id) + ftrack_id = None + if asset_doc: + ftrack_id = asset_doc["data"].get("ftrackId") + if not ftrack_id: + continue + + assetbuild = self.session.get("AssetBuild", ftrack_id) self.log.debug( "Creating link from {0} to {1}".format( assetbuild["name"], entity["name"] From 07751a929219f64d5514a42c30e0d466afadf345 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 17:37:29 +0200 Subject: [PATCH 164/258] hound fixes --- .../ftrack/event_handlers_user/action_delete_asset.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index b5b4ec5ac5..6dae3a4ca1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -94,7 +94,7 @@ class DeleteAssetSubset(BaseAction): ftrack_id = entity.get("entityId") if ftrack_id: ftrack_ids.append(ftrack_id) - + if project_in_selection: msg = "It is not possible to use this action on project entity." self.show_message(event, msg, True) @@ -149,7 +149,8 @@ class DeleteAssetSubset(BaseAction): continue ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] + end_index = len(ent_path_items) - 1 + parents = ent_path_items[1:end_index:] # TODO we should say to user that # few of them are missing in avalon for av_ent in av_ents_by_name: From 3d294edf6ef08441e71fdaf56c7b84e339de8a5e Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:55:48 +0200 Subject: [PATCH 165/258] Fixed logic and settings, uses webbrowser module --- openpype/modules/ftrack/ftrack_module.py | 3 + openpype/modules/ftrack/tray/ftrack_tray.py | 63 +++++++----- .../defaults/system_settings/modules.json | 15 +-- .../module_settings/schema_ftrack.json | 42 +------- tools/run_ftrack_eventserver.ps1 | 39 -------- tools/run_ftrack_eventserver.sh | 99 ------------------- 6 files changed, 47 insertions(+), 214 deletions(-) delete mode 100644 tools/run_ftrack_eventserver.ps1 delete mode 100644 tools/run_ftrack_eventserver.sh diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index f99e189082..048e5ebfb1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,6 +42,9 @@ class FtrackModule( self.ftrack_url = ftrack_url + ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] + self.ftrack_open_as_app = ftrack_open_as_app + current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 7ac994e967..065528dcff 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,9 +1,14 @@ +from hashlib import new +from operator import pos import os import time import datetime import threading import platform import subprocess +import posixpath, ntpath +import webbrowser +import shutil from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -14,7 +19,6 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources -from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -52,29 +56,42 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - cur_os = platform.system().lower() - settings = get_system_settings()["modules"]["ftrack"] - browser_paths = settings["ftrack_browser_path"][cur_os] - browser_args = settings["ftrack_browser_arguments"][cur_os] - browser_args.append(self.module.ftrack_url) - path = "" - for p in browser_paths: - if os.path.exists(p): - path = p - log.debug(f"Found valid executable at path: {p}") - break + env_pf64 = os.environ['ProgramW6432'].replace( + ntpath.sep, posixpath.sep) + env_pf32 = os.environ['ProgramFiles(x86)'].replace( + ntpath.sep, posixpath.sep) + env_loc = os.environ['LocalAppData'].replace( + ntpath.sep, posixpath.sep) + chromium_paths_win = [ + f"{env_pf64}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Google/Chrome/Application/chrome.exe", + f"{env_loc}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" + ] + cur_os = cur_os = platform.system().lower() + if cur_os == "windows": + is_chromium = False + for p in chromium_paths_win: + if os.path.exists(p): + is_chromium = True + chromium_path = p + break + if is_chromium and self.module.ftrack_open_as_app: + webbrowser.get(f"{chromium_path} %s").open_new( + f"--app={self.module.ftrack_url}") else: - log.warning(f"Path: {p} is not valid, please \ - doublecheck your settings!") - if path == "": - log.warning("Found no valid executables to launch \ - Ftrack with. Feature will not work as expected!") - return - args = " ".join(str(item) for item in browser_args).replace("= ", "=") - log.debug(f"Computed arguments: {args}") - cmd = f"{path} {args}" - log.debug(f"Opening Ftrack Browser...") - subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + webbrowser.get(using="windows-default").open_new( + self.module.ftrack_url) + + else: + if self.module.ftrack_open_as_app: + try: + webbrowser.get(using='chrome').open_new( + f"--app={self.module.ftrack_url}") + except webbrowser.Error: + webbrowser.open_new(self.module.ftrack_url) + else: + webbrowser.open_new(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index aaf01b1631..6d09652bb9 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,20 +15,7 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_browser_path": { - "windows": [ - "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" - ], - "darwin": [], - "linux": [] - }, - "ftrack_browser_arguments": { - "windows": [ - "--app=" - ], - "darwin": [], - "linux": [] - }, + "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 268c5479fe..570d856cf8 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -17,45 +17,9 @@ "label": "Server" }, { - "type": "splitter" - }, - { - "type": "path", - "key": "ftrack_browser_path", - "label": "Browser Path", - "use_label_wrap": true, - "multipath": true, - "multiplatform": true - }, - { - "type": "dict", - "key": "ftrack_browser_arguments", - "label": "Browser Arguments", - "use_label_wrap": true, - "children": [ - { - "type": "label", - "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" - }, - { - "key": "windows", - "label": "Windows", - "type": "list", - "object_type": "text" - }, - { - "key": "darwin", - "label": "MacOS", - "type": "list", - "object_type": "text" - }, - { - "key": "linux", - "label": "Linux", - "type": "list", - "object_type": "text" - } - ] + "type": "boolean", + "key": "ftrack_open_as_app", + "label": "Open in app mode" }, { "type": "splitter" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 deleted file mode 100644 index 9c22f3d88e..0000000000 --- a/tools/run_ftrack_eventserver.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS - Helper script to start OpenPype Ftrack EventServer without relying on built executables. - -.DESCRIPTION - - -.EXAMPLE - -PS> .\run_eventserver.ps1 - -#> -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - -$env:_INSIDE_OPENPYPE_TOOL = "1" -$env:OPENPYPE_DEBUG = "1" -# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" - -# make sure Poetry is in PATH -if (-not (Test-Path 'env:POETRY_HOME')) { - $env:POETRY_HOME = "$openpype_root\.poetry" -} -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" - -Set-Location -Path $openpype_root - -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." - & "$openpype_root\tools\create_env.ps1" -} else { - Write-Host "OK" -ForegroundColor Green -} - -& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh deleted file mode 100644 index 97daa14c2d..0000000000 --- a/tools/run_ftrack_eventserver.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -art () { - cat <<-EOF - - . . .. . .. - _oOOP3OPP3Op_. . - .PPpo~· ·· ~2p. ·· ···· · · - ·Ppo · .pPO3Op.· · O:· · · · - .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · - ·~OP 3PO· .Op3 : · ·· _____ _____ _____ - ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / - O3:· O3p~ · ·:· · ·/____/·/____/ /____/ - 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · - · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · - · '_ .. · . _OP3·· · ·https://openpype.io·· · - ~P3·OPPPO3OP~ · ·· · - · ' '· · ·· · · · ·· · - -EOF -} - -# Colors for terminal - -RST='\033[0m' # Text Reset - -# Regular Colors -Black='\033[0;30m' # Black -Red='\033[0;31m' # Red -Green='\033[0;32m' # Green -Yellow='\033[0;33m' # Yellow -Blue='\033[0;34m' # Blue -Purple='\033[0;35m' # Purple -Cyan='\033[0;36m' # Cyan -White='\033[0;37m' # White - -# Bold -BBlack='\033[1;30m' # Black -BRed='\033[1;31m' # Red -BGreen='\033[1;32m' # Green -BYellow='\033[1;33m' # Yellow -BBlue='\033[1;34m' # Blue -BPurple='\033[1;35m' # Purple -BCyan='\033[1;36m' # Cyan -BWhite='\033[1;37m' # White - -# Bold High Intensity -BIBlack='\033[1;90m' # Black -BIRed='\033[1;91m' # Red -BIGreen='\033[1;92m' # Green -BIYellow='\033[1;93m' # Yellow -BIBlue='\033[1;94m' # Blue -BIPurple='\033[1;95m' # Purple -BICyan='\033[1;96m' # Cyan -BIWhite='\033[1;97m' # White - - -############################################################################## -# Return absolute path -# Globals: -# None -# Arguments: -# Path to resolve -# Returns: -# None -############################################################################### -realpath () { - echo $(cd $(dirname "$1"); pwd)/$(basename "$1") -} - -# Main -main () { - echo -e "${BGreen}" - art - echo -e "${RST}" - - # Directories - openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - - if [[ -z $POETRY_HOME ]]; then - export POETRY_HOME="$openpype_root/.poetry" - fi - - echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" - if [ -f "$POETRY_HOME/bin/poetry" ]; then - echo -e "${BIGreen}OK${RST}" - else - echo -e "${BIYellow}NOT FOUND${RST}" - echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." - . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } - fi - - pushd "$openpype_root" > /dev/null || return > /dev/null - - echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." - "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver -} - -main From bf8b64ae3067224d56c986fd2ca3a00ea8943624 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 17:56:21 +0200 Subject: [PATCH 166/258] use query functions in TVPaint --- openpype/hosts/tvpaint/api/pipeline.py | 15 ++++++++------- .../hosts/tvpaint/plugins/load/load_workfile.py | 13 +++++-------- .../tvpaint/plugins/publish/collect_instances.py | 11 +++++------ .../plugins/publish/collect_scene_render.py | 11 ++++------- .../tvpaint/plugins/publish/collect_workfile.py | 11 +++++------ 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 60c61a8cbf..0118c0104b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -8,6 +8,7 @@ import requests import pyblish.api +from openpype.client import get_project, get_asset_by_name from openpype.hosts import tvpaint from openpype.api import get_current_project_settings from openpype.lib import register_event_callback @@ -442,14 +443,14 @@ def set_context_settings(asset_doc=None): Change fps, resolution and frame start/end. """ - if asset_doc is None: - # Use current session asset if not passed - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }) - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + if asset_doc is None: + asset_name = legacy_io.Session["AVALON_ASSET"] + # Use current session asset if not passed + asset_doc = get_asset_by_name(project_name, asset_name) + + project_doc = get_project(project_name) framerate = asset_doc["data"].get("fps") if framerate is None: diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py index 0eab083c22..462f12abf0 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py +++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py @@ -1,5 +1,6 @@ import os +from openpype.client import get_project, get_asset_by_name from openpype.lib import ( StringTemplate, get_workfile_template_key_from_context, @@ -44,21 +45,17 @@ class LoadWorkfile(plugin.Loader): # Save workfile. host_name = "tvpaint" + project_name = context.get("project") asset_name = context.get("asset") task_name = context.get("task") # Far cases when there is workfile without context if not asset_name: + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] - project_doc = legacy_io.find_one({ - "type": "project" - }) - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - project_name = project_doc["name"] + project_doc = get_project(project_name) + asset_doc = get_asset_by_name(project_name, asset_name) template_key = get_workfile_template_key_from_context( asset_name, diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 782907b65d..9b6d5c4879 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -2,6 +2,7 @@ import json import copy import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io @@ -92,17 +93,15 @@ class CollectInstances(pyblish.api.ContextPlugin): if family == "review": # Change subset name of review instance + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Collect asset doc to get asset id # - not sure if it's good idea to require asset id in # get_subset_name? asset_name = context.data["workfile_context"]["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] # Host name from environment variable host_name = context.data["hostName"] # Use empty variant value diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py index 2b8dbdc5b4..20c5bb586a 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py @@ -2,8 +2,8 @@ import json import copy import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc -from openpype.pipeline import legacy_io class CollectRenderScene(pyblish.api.ContextPlugin): @@ -56,14 +56,11 @@ class CollectRenderScene(pyblish.api.ContextPlugin): # - not sure if it's good idea to require asset id in # get_subset_name? workfile_context = context.data["workfile_context"] - asset_name = workfile_context["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - # Project name from workfile context project_name = context.data["workfile_context"]["project"] + asset_name = workfile_context["asset"] + asset_doc = get_asset_by_name(project_name, asset_name) + # Host name from environment variable host_name = context.data["hostName"] # Variant is using render pass name diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index 70d92f82e9..88c5f4dbc7 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -2,6 +2,7 @@ import os import json import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io @@ -22,19 +23,17 @@ class CollectWorkfile(pyblish.api.ContextPlugin): basename, ext = os.path.splitext(filename) instance = context.create_instance(name=basename) + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Get subset name of workfile instance # Collect asset doc to get asset id # - not sure if it's good idea to require asset id in # get_subset_name? family = "workfile" asset_name = context.data["workfile_context"]["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] # Host name from environment variable host_name = os.environ["AVALON_APP"] # Use empty variant value From 6a4387a866d52e027d74805a54fe4f8a43004c38 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:58:56 +0200 Subject: [PATCH 167/258] finxed hounds --- openpype/modules/ftrack/tray/ftrack_tray.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 065528dcff..70f6e69323 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,14 +1,12 @@ -from hashlib import new -from operator import pos import os import time import datetime import threading import platform -import subprocess -import posixpath, ntpath +import posixpath +import ntpath import webbrowser -import shutil + from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -82,7 +80,7 @@ class FtrackTrayWrapper: else: webbrowser.get(using="windows-default").open_new( self.module.ftrack_url) - + else: if self.module.ftrack_open_as_app: try: From 780ffefea972a3fb1b0c225136c1327878b95a91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 18:08:28 +0200 Subject: [PATCH 168/258] fix unhandled empty source on instance --- openpype/plugins/publish/integrate_new.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 91f6102501..2471105250 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -940,9 +940,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families += current_families # create relative source path for DB - if "source" in instance.data: - source = instance.data["source"] - else: + source = instance.data.get("source") + if not source: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) From e98f81c70c4d8054f3cd2b8ef4ce6e0e1b7b11ba Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 19:17:54 +0200 Subject: [PATCH 169/258] made the browser opening non blocking --- openpype/modules/ftrack/tray/ftrack_tray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 70f6e69323..e822fd4639 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -90,6 +90,7 @@ class FtrackTrayWrapper: webbrowser.open_new(self.module.ftrack_url) else: webbrowser.open_new(self.module.ftrack_url) + return def validate(self): validation = False From ae9064ac5960b602c324b8d7aa6105725f4b70ea Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 15 Jun 2022 03:56:39 +0000 Subject: [PATCH 170/258] [Automated] Bump version --- CHANGELOG.md | 39 ++++++++++++++++++--------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d25908cd..eb71071205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -11,7 +11,9 @@ **🚀 Enhancements** +- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) - updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) - TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) - Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) - Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) @@ -20,15 +22,18 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) - Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) - Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) -- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) **🐛 Bug fixes** +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) +- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -38,18 +43,24 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +**🔀 Refactored code** + +- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) +- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) + **Merged pull requests:** - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) +- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -57,32 +68,26 @@ **🚀 Enhancements** -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) -- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) **🐛 Bug fixes** +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) - Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) -- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) -- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) - -**🔀 Refactored code** - -- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) **Merged pull requests:** @@ -98,18 +103,10 @@ - nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) - Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) -- Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195) -- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) **🐛 Bug fixes** - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) -- Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) -- Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) - -**Merged pull requests:** - -- hiero: otio p3 compatibility issue - metadata on effect use update [\#3194](https://github.com/pypeclub/OpenPype/pull/3194) ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) diff --git a/openpype/version.py b/openpype/version.py index 4b0a688cbf..2f4d180983 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.2" +__version__ = "3.11.0-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 4289c74ebe..e1b5c37289 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.2" # OpenPype +version = "3.11.0-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 180fd1f843b9a7d7fa9046964c1f155b17412c94 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 13:40:57 +0900 Subject: [PATCH 171/258] We want to strip namespaces by default for mvUsd. --- openpype/hosts/maya/plugins/create/create_multiverse_usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index 034714d51b..adf0acc7c2 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -16,7 +16,7 @@ class CreateMultiverseUsd(plugin.Creator): self.data.update(lib.collect_animation_data(True)) self.data["fileFormat"] = ["usd", "usda", "usdz"] - self.data["stripNamespaces"] = False + self.data["stripNamespaces"] = True self.data["mergeTransformAndShape"] = False self.data["writeAncestors"] = True self.data["flattenParentXforms"] = False From 5af098d18afc6156165964fcd696e35afd0bf6be Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 16:14:39 +0900 Subject: [PATCH 172/258] Adding Options to the main Studio Settings. --- .../defaults/project_settings/maya.json | 8 ++++ .../schemas/schema_maya_create.json | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4cdfe1ca5d..aaf47479b8 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -159,6 +159,14 @@ "defaults": [ "Main" ] + }, + "CreateMultiverseLook": { + "enabled": true, + "publish_mip_map": true + }, + "CreateMultiverseUsd": { + "enabled": true, + "strip_namespaces": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 6dc10ed2a5..4dd54c81a0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -124,6 +124,44 @@ ] }, + { + "type": "dict", + "collapsible": true, + "key": "CreateMultiverseLook", + "label": "Create Multiverse Look", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "publish_mip_map", + "label": "Publish Mip Maps" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateMultiverseUsd", + "label": "Create Multiverse USD", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "strip_namespaces", + "label": "Strip Namespaces" + } + ] + }, { "type": "schema_template", "name": "template_create_plugin", From 1d22b862d1e95b56e64b8ae8db7234f7ed6eeb5e Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 18:39:31 +0900 Subject: [PATCH 173/258] Adding ExtractAlembic to settings and setting the defaults to what they were, so nothing changes here except the option to remove certain items from families. --- .../defaults/project_settings/maya.json | 24 ++++++++++++------- .../schemas/schema_maya_publish.json | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index aaf47479b8..df54e44c56 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -65,6 +65,14 @@ "defaults": [], "joint_hints": "jnt_org" }, + "CreateMultiverseLook": { + "enabled": true, + "publish_mip_map": true + }, + "CreateMultiverseUsd": { + "enabled": true, + "strip_namespaces": true + }, "CreateAnimation": { "enabled": true, "defaults": [ @@ -159,14 +167,6 @@ "defaults": [ "Main" ] - }, - "CreateMultiverseLook": { - "enabled": true, - "publish_mip_map": true - }, - "CreateMultiverseUsd": { - "enabled": true, - "strip_namespaces": true } }, "publish": { @@ -383,6 +383,14 @@ "optional": true, "active": true }, + "ExtractAlembic": { + "enabled": true, + "families": [ + "pointcache", + "model", + "vrayproxy" + ] + }, "ValidateRigContents": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 2e5bc64e1c..bbe0418139 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -490,6 +490,30 @@ "label": "ValidateUniqueNames" } ] + }, + { + "type": "label", + "label": "Extractors" + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractAlembic", + "label": "Extract Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + } + ] } ] }, From 3977c25a804671e04d6c688346e1be5a485187e9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:31:36 +0200 Subject: [PATCH 174/258] Nuke: improving logic for `useSequenceForReview` argument --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 5 +++++ .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 8669f4f485..a0b4b77a2d 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -175,6 +175,11 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "frameEndHandle": last_frame, }) + # make sure rendered sequence on farm will + # be used for exctract review + if instance.data["review"]: + instance.data["useSequenceForReview"] = True + # * Add audio to instance if exists. # Find latest versions document version_doc = pype.get_latest_version( diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..3c036510b3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True) + "useSequenceForReview": data.get("useSequenceForReview") } if "prerender" in instance.data["families"]: From 21d68b0d13914a13bbfc12040ee342b260a116ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 14:35:40 +0200 Subject: [PATCH 175/258] use get last version for subset in collect published files --- .../publish/collect_published_files.py | 74 ++++++------------- 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index bdd3caccfd..20e277d794 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -13,9 +13,13 @@ import tempfile import math import pyblish.api + +from openpype.client import ( + get_asset_by_name, + get_last_version_by_subset_name +) from openpype.lib import ( prepare_template_data, - get_asset, get_ffprobe_streams, convert_ffprobe_fps_value, ) @@ -23,7 +27,6 @@ from openpype.lib.plugin_tools import ( parse_json, get_subset_name_with_asset_doc ) -from openpype.pipeline import legacy_io class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -56,8 +59,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): self.log.info("task_sub:: {}".format(task_subfolders)) + project_name = context.data["project_name"] asset_name = context.data["asset"] - asset_doc = get_asset() + asset_doc = get_asset_by_name(project_name, asset_name) task_name = context.data["task"] task_type = context.data["taskType"] project_name = context.data["project_name"] @@ -80,7 +84,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): family, variant, task_name, asset_doc, project_name=project_name, host_name="webpublisher" ) - version = self._get_last_version(asset_name, subset_name) + 1 + version = self._get_next_version( + project_name, asset_doc, subset_name + ) instance = context.create_instance(subset_name) instance.data["asset"] = asset_name @@ -219,55 +225,19 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): config["families"], config["tags"]) - def _get_last_version(self, asset_name, subset_name): - """Returns version number or 0 for 'asset' and 'subset'""" - query = [ - { - "$match": {"type": "asset", "name": asset_name} - }, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "_id", - "foreignField": "parent", - "as": "subsets" - } - }, - { - "$unwind": "$subsets" - }, - { - "$match": {"subsets.type": "subset", - "subsets.name": subset_name}}, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "subsets._id", - "foreignField": "parent", - "as": "versions" - } - }, - { - "$unwind": "$versions" - }, - { - "$group": { - "_id": { - "asset_name": "$name", - "subset_name": "$subsets.name" - }, - 'version': {'$max': "$versions.name"} - } - } - ] - version = list(legacy_io.aggregate(query)) + def _get_next_version(self, project_name, asset_doc, subset_name): + """Returns version number or 1 for 'asset' and 'subset'""" - if version: - return version[0].get("version") or 0 - else: - return 0 + version_doc = get_last_version_by_subset_name( + project_name, + subset_name, + asset_doc["_id"], + fields=["name"] + ) + version = 1 + if version_doc: + version += int(version_doc["name"]) + return version def _get_number_of_frames(self, file_url): """Return duration in frames""" From a7f2587483ed5ea31090f1658937cb3904043fb6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:45:57 +0200 Subject: [PATCH 176/258] Nuke: reversing logic so it will not break other hosts --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 4 ++-- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a0b4b77a2d..e050fc8c52 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -177,8 +177,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): # make sure rendered sequence on farm will # be used for exctract review - if instance.data["review"]: - instance.data["useSequenceForReview"] = True + if not instance.data["review"]: + instance.data["useSequenceForReview"] = False # * Add audio to instance if exists. # Find latest versions document diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 3c036510b3..0583c25b57 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview") + "useSequenceForReview": data.get("useSequenceForReview", True) } if "prerender" in instance.data["families"]: From 65a22a1ae98fb2489bd32f6f122fa60f84261162 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:55:02 +0200 Subject: [PATCH 177/258] removing KnobScripter also default mov colorspace --- .../nuke/startup/KnobScripter/__init__.py | 1 - .../KnobScripter/icons/icon_clearConsole.png | Bin 1860 -> 0 bytes .../KnobScripter/icons/icon_download.png | Bin 1225 -> 0 bytes .../KnobScripter/icons/icon_exitnode.png | Bin 1883 -> 0 bytes .../startup/KnobScripter/icons/icon_pick.png | Bin 2184 -> 0 bytes .../startup/KnobScripter/icons/icon_prefs.png | Bin 2277 -> 0 bytes .../KnobScripter/icons/icon_prefs2.png | Bin 2758 -> 0 bytes .../KnobScripter/icons/icon_refresh.png | Bin 1778 -> 0 bytes .../startup/KnobScripter/icons/icon_run.png | Bin 2341 -> 0 bytes .../startup/KnobScripter/icons/icon_save.png | Bin 1784 -> 0 bytes .../KnobScripter/icons/icon_search.png | Bin 2400 -> 0 bytes .../KnobScripter/icons/icon_snippets.png | Bin 1415 -> 0 bytes .../startup/KnobScripter/knob_scripter.py | 4196 ----------------- openpype/hosts/nuke/startup/init.py | 4 - 14 files changed, 4201 deletions(-) delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/__init__.py delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_download.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_search.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py delete mode 100644 openpype/hosts/nuke/startup/init.py diff --git a/openpype/hosts/nuke/startup/KnobScripter/__init__.py b/openpype/hosts/nuke/startup/KnobScripter/__init__.py deleted file mode 100644 index 8fe91d63f5..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import knob_scripter \ No newline at end of file diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png deleted file mode 100644 index 75ac04ef84b7235c071b512d1dc60410c32c5834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1860 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 VW=lh7gf9R9002ovPDHLkV1jt?fvf-k diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png deleted file mode 100644 index 239553755060aa93162d8748796d7ea29bb7b07e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2184 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 M07*qoM6N<$f-?anq5uE@ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png deleted file mode 100644 index 559bfd74ab8b550503194ba2409795e77a52c83b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmV%no+>XdU+wjJ?^&>5L4iOZ zg#>9CnO#2f)tS$>ZQbU}7r>G$mr6ni(b(9yZR4hmL2a?Nbf%*}g|Y^57rBdfwD0Jv ztE+n&LB@Fr^Ok3;2KYPR$f#rmA`qf|SNmW6wf=xWAf*HWQc47Zsi`TZ{x^k?#t6;# z_v+QFPWYc2NMOT;wz^t>KngG!xO8cZXf#SJ9w#w6>Zo(sNExKGwDhh`n>Gd|fCM4n z?Aal9?b^lo__+PmYrv0+&Hh*2)%m3ivVQ&g-+MeBF8~5ACN8pb=T0UjCIJ2e>;c{b zhVyl*va<5AbLYdDI{&g@nx38Da=WBQ+^h(s_autDL!?LFeqdc|d&?Vqv9`ykOA`}XxArKhx#uiEI z{B<{lQ3Men%!aLq4Oen=JtLpb*AVImMO}p^1Oj1>`pmN5?{5qSJE8>z1#|ASiXakc z0tl|}t-K+MTt!H8H$)=SeP%BkJ?Hn=Hg<$M-kA$VeQKEoG6RyIs;a7c?iWA$=)+@3NTpJQ z!(n3aIGDK@z~tm4t!=FajDV4mf7905<{29s>t;qUR|auvB+~6kDgWHr`TgO;hY9Zv z6N|+_kTOQy%z=Q3@rf_n+FHN);`1+Pd+8+-i3GqJV5z-5P zfh3YYf!JZp;k31j;}a9VZEtTs4y*#c2Y!K(7NwQb!OB5N;00g`hyypu?ME@3-w(6^ z|C;%N8i){?9&&zYB(TM7``76wvo(s54$UdGH6k-}*73!1ogG_L&r z_qGEuQj+ZJ-4MjR(7ni8=Dpi0TWQJ66lKYhCHIQ-0?JcXD>-Z?bAl{fxNyhA9P)a&({Fd3AUD_0hEb#)Gum6a_E1Olxsn_If_9vR#1Ox_?(O-+AzWbGrrvPO5s ziskpe@y4E`W-bZ{N=rR-c6Kf+)4a0}FetQXa%0g!HVNGNoK)zJQp9iN#}P zHyQhqMAF~qJj7y6Taq3R3F8yv^H;FxW|4hb*Y)ObBy#9e^YkXAxMd^pU}fdwlFS;p zaNz<#Ql+j+8b6)X_2%8-@PQ6!I)DEBErHN9ZE1adeY1TU4-O6jTu`a6da#;KCX>J0 z{p#)mDVb_ApE(90gb>?b-u|Yi#G^@!8;(RG!P%j+0RL2JBbyWAbV{b0fdjzTI0WG> z^WMGvqwVAgh0QsU97s@AT<3~0iZH_a>q zEMBZFUA5|gM{8^Sf#Tvto=gCS!Bc~LeB>w?xBFkJIOf<{ue`i`bx%*v8LQRnu069T z4-XHsb?a8^lKeTKSH-?_=MmdYq;+I9d{i%=Jb98Wo3~gHp8)-;<{iI9kygGm<3s7J za{9VYGBPqkZ*MOH0|Ul;aE=>H;ad)J<;s=GSS)r_NU`o_Uv!f)$=Iba5~B%Doj%34 z-+XIt>X->J=9F)_Xf?(p%jNeS@GLn_cc-V;B=D}{yGe}O!eJl=3_9$~9sQ*GKkH)C Uw!?IYy#N3J07*qoM6N<$f)br{CIA2c diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png deleted file mode 100644 index 6b2e4ddc2348254824b75b9d14b03c5eb28430fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2341 zcmV+=3EK9FP)@28X)BXK4_L%0;0i^ZWhi?r}hV z*v}W9YceC#urMm2nF3QtEQ$hS=7ZC_J@-hGib}X~Arr76AbjgV-n) zZOk#|FV0?U@^GH1!GXc+9LG(?HrsGyW@J8-zcRn1udlabe0<`LT3u023Q3x&0OeyT zj%C@U-5R$X0v(3q36f(l!~@Av!9wVpeE^!G=9HZ?glraA)#mOQ&8@9>escD+t- z7@HWIuvS~k@7}%J9bu|b=|>^bq9I|0n+q1Is;b`p<^KJjvLPAx1f{7l-mxTtt@R#w&$3LrIAn^Ia@)&gLaSbpWo)pH!@@rqHE z(PVqSh&!Ttz2Ho0x0{p=N+!WMl*Yo`(>c zj*N`BcJHqLacODk@f$a8XzX@709=ud(bm@1ym8Z}@AH0_!eX)D*s)`;0O*}5E(!W) z8kTZKDX=`2mUoblkeDc@b`*Sa0l*=A;Og(UyZZZo0H9jZ>`Q4M-QC?My1Tna0Bi+t z2|%l~ytA{j#mjL%tyY^%2!YXPgvn&uF+4oHS3X~t)O0;PEnWX$`az(suI@)pr}Kn-9Iw~wvDs{AH*VVaviLNjqM{yS)jg9`=H{L)`Pfq~AdDdKnAmlS3WV*Szxp@FqvL~)6 ztFpk5q=#zj_0BuL-uT{;BUd?&n^Cc=>pEV0?WeeP>((gv>z9DE?a0W;g;S?aecS1D z?gp?PD)(ilPoF*%2uuekrMP$R9vlt_02DIk9gOAKP>8f>xSc47OhIXLbMx9WXU^;? zDk|EOot>@qcsy`8dU5mSFuAf~eaG^>{o7E?>U<8vvUDm;p=z_$PqANWw%5m&^4Bm&IsVOO_tgK82NKK^I7zmN3XfiIotOgdJ+1Kn|p-{IDwDTF9QpsBmat?uzrs(B^It-^E93X3?xCx@ z3-|r^zW^VC`lA;(RK6a-jK?AK7!v?40`NWQiNp~{9C5@EM+E#Ie<;2-)aFSU00000 LNkvXXu0mjfRQGr7 diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png deleted file mode 100644 index e29c667f3455f4afa92f63ba8de4ce3c4b53a866..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1784 zcmV+f?nK~v}qSZF0fhEyVF|mpGAYx!?s%GY?F1qL{o1(s$SZj9$F?o=Z*azEe z5&XkG>C)YIg^qROn{~A<2#Zi#vx3;hAVph<+Lq2Fo%#82&$)ljJ(IbU%ox}07l!0} z&)jo=|IY9He&-Hc!4+IVBcbdISEbV}PmhjH-+SMEkF~eAUk?hDpajI+QiUrehy;&V z4WF5r`TPF;`=2V8%cWWrYql@!*zwFO5B=f6XVd9)tM?og5;{4@$pa%K<+t2=^POFt zn{GUE^jKa*!ZR<6lzrj;`|o|syIlc-LIO`9%)CFt8?V2?#fuk{KBH!2G8y*l*~6wy zT_|8Uo82xbH9fueiBc(<=VBfk8y%fCmID*39xs(j?0R+=mo8nZivp?U6m7q0Bb~mA zRB9bdOG^MOEG$qg78xA8$9pu8$=p29xvBG}V@HlA@?4XBK>(%jV=K%Hg#yK55#RuD zt*7zIJ{vb~{O!T~!5{PaJo)^)y!Wp|jE#={)r_AzcW!4i%!O<=H!?mx{*R`nrkF#j zDuz-7D_NT>YM{%&4&aAslP`zP&d#3J*0y$K(w9JYPxnAbkki33K(AF{IGg>`_{7A4 z<|@j?6%#>B0BUoIQP9Ihpf0hf5JZ$A06AdVHKsyx<+8&gFN{wdSk7~C#n7Qg;HYqD z))QWs3!uXD_qoQxTm-V&;gN}n%F4wQ^CM5WdEFt}YS#STbAvG#A0Ez*j8BX^!?Hs1 zFe_GByxvfV(g9}8FJ?~z5T(V5^W1QDPGZf>gQ8mb8rebl;A0E?NkAQ^a zGW>=cI-bDVy4;FIG9zZMZU{tq8^3FQdrWj@bjW!t@O=2*3LevoN?E+#@UtC3#tIKx zW3lf>vK!sudn)NlUz%)t>&>@*clPYrZT9q4JFq0apwxpa zrj&%LM4jy;%gW-emEx#8yl+YkJXES$d|Ytz7&~+dL}~Pm=CUa`y}wBvqTAIdg{d=g-F`tQVA0bai#n*4CyYBz&x7^EezE zv%u=+Bj5=7L&gZWHvZ{Ib!T+Wv(R$4h+=G>r7)Qg{uJYHN{Vs>^G;0xd(;Pb|4 z6oEQ^{P;g}x!j1^#Lb58^CaoszY%;F8@{x-NV!}Fc#@w-DF9hqEY3t%Ql&yFfK#zZ zGF7n-2v!eIh(HvZ#}ThQtexEEYl9$p zdCJ&44#$BhwT5%Uu@aG-B~`q*Tw9k#JCk@nk*K87HHyB+uBO)pri1q@`pB_(h=<_z zn6FTXuc6btc8JP>fd2me4JbDFub6a4ZzC3pWs!1_cJx1M zoEd{-ZCxq)JCH7hlXTEu&&|#D^z__rl<_AONTpH?4Gpam%BWj*4|-K@JdPedTu^$4Ew_1;NZ5Ym!|UTn%4!{MnU}8Idqt!4|=Ev_l=p1&&|y- zHa13~Q1~10Ohv^2GMUVmTeoh_v|rQivh-zy!}ah)HSPt9pgi6QY&N`1nfbYS4!w7X zLZJY#3wYh7PZ-;PMS_+2OaGh%+QJGfmlEy(9sxE~v%(sp4Ez^(6YqyLKHRTre+Br} aD)tkwJPu`6Ec-S90000yBjY@lTK?(Xe}yLVYEo0pr= zD#QQVvv=<~&u{N}&U5a07H;T+INX�v)R0;sIYMD(p#66WSO%Tr>3PYNSu{8 z8yQAMd;{nH)z{P2-R*X{T!)SvJ#ygUg^MFI;deY~2Gi+u`n&G>-n#Yc%b!`aDEAH_ zK$caLGV#WeB>C-j`<|U|zWL(GFTZS`8I1pyCa@)omlRb!UG=Ih$CgiI@&XxIVVKky z!t%TCzSrE;)U?It^9{}fOsu8^CWH_b6%TK%tbC+Sr_<{rvy42A$gBkj83K}o!C*w_ zl(Ec}*VXaBz(DV|ZQC}sx3@e0FJOAT-mrP|mgY70lvOAbi`*_`!od(Ow;QL^iQDbL z>m6Wtcm#vNKuSsq3l=P-pkM_hCB<8>XAJ7jNfq6gfE{M%11R}^0pNb%w}5vFe!l>kdU|?vUaxo6%9SgH5Xvu<^6iwH z0)aq^$K!cV8&YKM<6SH*E!|+V+45!8s~o1jp@F`$X8}F~3V=fzeTRVOTU%Q5#p{O$52|abALU7XIUVN_{q?m!zE(9lIvN?u zBqt~9@4Ij9pQmFqE|}SD$;->jy#tgzDijLR{QB#PXV}2ZKmjm%Vf2-qJ9i#cQi$T< zo_p4m0V!H-#sw=XDqJhe%H|6)pML7*tP+B6p*{TK>VpqHs0@cg$jTm$)n?VFrKLTs z)m~h%Wy_Y8#3s!nN7?`+;CZdi#?#Qy(20L^bexP6Fe6n#JoWeYACE3FVBWlW z0C#G!byYASgfN**iAt6iNPz%8pAW9V+KScC(9k(qmLuyLX3a_hSggg?Rl#IgR>K?s zys7MtTva@Ve1)yQ%r{esPEO{l2&>0h)sil@ZH#H7ueH#h5a%6@_@^Tml1 zYNhi&5YS?G+$`tydQYXNr&r!`OU@j%ZMJ-Q{;elZp6obv>Qtu|hp|$7DYxH#dzo5F z$zhqg`a1gi`vHCd^k}g?Vft=wZ}*gyt*M+ndzLyKN=iz8G%zr5rmL&VHSW#hC@~>1 zsivmpjneOzmaCr$1bg@I<(+rl0r&){(Q1D}Fu&j5bNu-6!n^NYontU4(W2Mub;ZRc z52mEtl-kkJaWoJJj7`O<&1TErv17;Ex7~JInOgCx+t>N?{WzVDa5yZl1I|nxNztS> z+;iv7z2Db&_VME4l0>2A4gqblp_Ws90;ZUgepJ%@C`~8>3vy+{jz4VDEp7_l# ztEzsVHg|4Dw0lYvkF0D97PF<>>2&@&91e%If=#6pd}qn3RUg)F-D)r>s{IMiSQ_hT2 zt^L})UT=GAYwLrOBpJS&n@fVx7`Y^3BMa31FV6VHi4(l|(n};IC1EyOh$#`H-?dmQ zc@~Rhsk5zZUpO2NT}v>4&qJY5tHZs4h&8ex(Nl8)hh$V;}OOk}s;pEjn zyh>A36TQ8?IP4CrHY=HCbJT%AD3Oi=mu0amZJQ$8bPoO!foFkV0XNP6&V2Hh-XAN>OZbar%b{PWMbG!TX*VR2xTwHwh@J8Tp9Nxce zdt-TUl3_U$Cz^ABO+W>(bQ0h9qqG?Q1pE!<(VC0~Mgm5!*R2w4;N{=EB<;{0?1Y{8C1&#tAqtt+3mC0x{?5?Y;SKzKV3B4TJPN(zj zhK7a>!Ql9CGYvLfnT$r`?z%`=s#1@wlmWCk+FI%x>er74cTL}jH7FDghxgbWj^#G1 zHP>u5U%AJjJc&%RIoD>hEq6E^d*UUOuNxRd;2aLea;w#vdquVsBDy7*&E{OI)fyLW z#^0@GNC8Qb)-^OV9B6B6i+-rI<}RZe%CS^bUuQ5-;p~p(R+}}~Y*AW_ZnvB2>T0#`cn;Wwa<1@=lgVH-wlp?2 z$}hkCvYeQZAfx1zL%>x-@tY)*(P(T*xXz$dnNa?faER*&N|lN05bB1mTm1)mZLGb( S3=G}?0000A0%^ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png deleted file mode 100644 index 479c44f19e3c0c824c0491b4c176123eb1547197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmV;21$g?2P)KAK4h#sKNeKn0cL4TArF8tM(uGMt}G2RbOnLZ=zHx zSMSNqJt>xm{eAib042}P^L*}=0s!LuM`SWtz*40$z45-m)Ya8xnAi~>9v-dHXnvPU zrQFeD$Nn7X>l<6YVZ*ypsgyrFJnX)F`EtjVD_1(6JbB_AbB(#>tXvo~H%7i@^_n@c zF|h%4cj~(K74B;+DlS?a8yzdB0sH{rv+@mgOV>#pu|m zr|EuE&%%U-K_-*2f6JDv#mwHld1u4I!lGJRTWz;**Y)^B0D_vDs}`ft*bX2Yz##yw z02}~D0Camio*U)mWs5pH%{G>0CEgMeFoYm4KdKiV!w6y45F4riicMTaI zJ32ZZewmT6-DEN?gD)+BNF@>hUmzR-s6Y9xprB^Shsh~6TbENTmbj%-sm$kmLMXgt zd^N7uH{4XI_Gefumd7C>A#atJm){Hu3Jz;*)ITMDe7>-xxVSm~{RMNp-lflMJcWC+ z^x2qBvvy!$V5qRLaQ(O0*~LLKgB7hUZT9Wkx7{HCz_M(xTCHwYC=@eZ_kzBNTc277 zGdE|}_1JrwCtikdg<`=E2M^Y9GMTX5WE{M7@sg31v%&y*fPiH=ft+InLb2G-Un=2A zB>sG{SmY-Z3K*Y5y}k9_U4o$@himKBZMWOn+S&l*OnHLh;^NXuN=km_^ZCp{wffdC z=g+GEumHI6Q3gN+Kng&@Fg#IIRAf-nlBAGzYuCNwC-7rH0k_LdcjxY|Z>VoD0@w}U zFWzJ!tJV6@Y&H)kCnqmUNJxm_@pwo~Oq3)gC51#rMh*-O4OQCh_M-qU0Qi+sdakFZ z=h%Y>53VVfDp$!lIZFsZsjBpkT3u}ifIFMbe8#iwJ8a@k87>nR~P0fXy zw{D#n931RtSvD{xCT7tGNlBlF&YCrc0yuu+M9Y!Ghg$%Y1DJSJpDP3aSy@?CX=!OI zynQ8@koy;Zc9_b_%J#CdvL*m$0i2o~Z%XMVL$BALi;j*?3k?ko27nv2wU*)0Q8z;f zal2f!%Vrxk7z{mOVPXCZ!;pLT?%4{9iW)s0PZfY8)5U!*ghGM8T7A%@(`o7H)2E#r z#}xqB58${+BtBPBQPHi{YN%yH=;8DwV2}$KwSu z48tobD!QoCYU!CXX9fai%%}!X3P3nroM}tv!^p_U-v)!>#4c^AT z{j&Wp&BzC<)!N+N-rg4*8~c&N;c%6fmNwPb*P8+4188~U3|@dl06zivaeV&Y!av`O VU4IwAq!s`G002ovPDHLkV1g(>ppyUq diff --git a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py b/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py deleted file mode 100644 index 368ee64e32..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py +++ /dev/null @@ -1,4196 +0,0 @@ -# ------------------------------------------------- -# KnobScripter by Adrian Pueyo -# Complete python script editor for Nuke -# adrianpueyo.com, 2016-2019 -import string -import traceback -from webbrowser import open as openUrl -from threading import Event, Thread -import platform -import subprocess -from functools import partial -import re -import sys -from nukescripts import panels -import json -import os -import nuke -version = "2.3 wip" -date = "Aug 12 2019" -# ------------------------------------------------- - - -# Symlinks on windows... -if os.name == "nt": - def symlink_ms(source, link_name): - import ctypes - csl = ctypes.windll.kernel32.CreateSymbolicLinkW - csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) - csl.restype = ctypes.c_ubyte - flags = 1 if os.path.isdir(source) else 0 - try: - if csl(link_name, source.replace('/', '\\'), flags) == 0: - raise ctypes.WinError() - except: - pass - os.symlink = symlink_ms - -try: - if nuke.NUKE_VERSION_MAJOR < 11: - from PySide import QtCore, QtGui, QtGui as QtWidgets - from PySide.QtCore import Qt - else: - from PySide2 import QtWidgets, QtGui, QtCore - from PySide2.QtCore import Qt -except ImportError: - from Qt import QtCore, QtGui, QtWidgets - -KS_DIR = os.path.dirname(__file__) -icons_path = KS_DIR + "/icons/" -DebugMode = False -AllKnobScripters = [] # All open instances at a given time - -PrefsPanel = "" -SnippetEditPanel = "" - -nuke.tprint('KnobScripter v{}, built {}.\nCopyright (c) 2016-2019 Adrian Pueyo. All Rights Reserved.'.format(version, date)) - - -class KnobScripter(QtWidgets.QWidget): - - def __init__(self, node="", knob="knobChanged"): - super(KnobScripter, self).__init__() - - # Autosave the other knobscripters and add this one - for ks in AllKnobScripters: - try: - ks.autosave() - except: - pass - if self not in AllKnobScripters: - AllKnobScripters.append(self) - - self.nodeMode = (node != "") - if node == "": - self.node = nuke.toNode("root") - else: - self.node = node - - self.isPane = False - self.knob = knob - # For the option to also display the knob labels on the knob dropdown - self.show_labels = False - self.unsavedKnobs = {} - self.modifiedKnobs = set() - self.scrollPos = {} - self.cursorPos = {} - self.fontSize = 10 - self.font = "Monospace" - self.tabSpaces = 4 - self.windowDefaultSize = [500, 300] - self.color_scheme = "sublime" # Can be nuke or sublime - self.pinned = 1 - self.toLoadKnob = True - self.frw_open = False # Find replace widget closed by default - self.icon_size = 17 - self.btn_size = 24 - self.qt_icon_size = QtCore.QSize(self.icon_size, self.icon_size) - self.qt_btn_size = QtCore.QSize(self.btn_size, self.btn_size) - self.origConsoleText = "" - self.nukeSE = self.findSE() - self.nukeSEOutput = self.findSEOutput(self.nukeSE) - self.nukeSEInput = self.findSEInput(self.nukeSE) - self.nukeSERunBtn = self.findSERunBtn(self.nukeSE) - - self.scripts_dir = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Scripts")) - self.current_folder = "scripts" - self.folder_index = 0 - self.current_script = "Untitled.py" - self.current_script_modified = False - self.script_index = 0 - self.toAutosave = False - - # Load prefs - self.prefs_txt = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Prefs.txt")) - self.loadedPrefs = self.loadPrefs() - if self.loadedPrefs != []: - try: - if "font_size" in self.loadedPrefs: - self.fontSize = self.loadedPrefs['font_size'] - self.windowDefaultSize = [ - self.loadedPrefs['window_default_w'], self.loadedPrefs['window_default_h']] - self.tabSpaces = self.loadedPrefs['tab_spaces'] - self.pinned = self.loadedPrefs['pin_default'] - if "font" in self.loadedPrefs: - self.font = self.loadedPrefs['font'] - if "color_scheme" in self.loadedPrefs: - self.color_scheme = self.loadedPrefs['color_scheme'] - if "show_labels" in self.loadedPrefs: - self.show_labels = self.loadedPrefs['show_labels'] - except TypeError: - log("KnobScripter: Failed to load preferences.") - - # Load snippets - self.snippets_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Snippets.txt")) - self.snippets = self.loadSnippets(maxDepth=5) - - # Current state of script (loaded when exiting node mode) - self.state_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_State.txt")) - - # Init UI - self.initUI() - - # Talk to Nuke's Script Editor - self.setSEOutputEvent() # Make the output windowS listen! - self.clearConsole() - - def initUI(self): - ''' Initializes the tool UI''' - # ------------------- - # 1. MAIN WINDOW - # ------------------- - self.resize(self.windowDefaultSize[0], self.windowDefaultSize[1]) - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.setObjectName("com.adrianpueyo.knobscripter") - self.move(QtGui.QCursor().pos() - QtCore.QPoint(32, 74)) - - # --------------------- - # 2. TOP BAR - # --------------------- - # --- - # 2.1. Left buttons - self.change_btn = QtWidgets.QToolButton() - # self.exit_node_btn.setIcon(QtGui.QIcon(KS_DIR+"/KnobScripter/icons/icons8-delete-26.png")) - self.change_btn.setIcon(QtGui.QIcon(icons_path + "icon_pick.png")) - self.change_btn.setIconSize(self.qt_icon_size) - self.change_btn.setFixedSize(self.qt_btn_size) - self.change_btn.setToolTip( - "Change to node if selected. Otherwise, change to Script Mode.") - self.change_btn.clicked.connect(self.changeClicked) - - # --- - # 2.2.A. Node mode UI - self.exit_node_btn = QtWidgets.QToolButton() - self.exit_node_btn.setIcon(QtGui.QIcon( - icons_path + "icon_exitnode.png")) - self.exit_node_btn.setIconSize(self.qt_icon_size) - self.exit_node_btn.setFixedSize(self.qt_btn_size) - self.exit_node_btn.setToolTip( - "Exit the node, and change to Script Mode.") - self.exit_node_btn.clicked.connect(self.exitNodeMode) - self.current_node_label_node = QtWidgets.QLabel(" Node:") - self.current_node_label_name = QtWidgets.QLabel(self.node.fullName()) - self.current_node_label_name.setStyleSheet("font-weight:bold;") - self.current_knob_label = QtWidgets.QLabel("Knob: ") - self.current_knob_dropdown = QtWidgets.QComboBox() - self.current_knob_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateKnobDropdown() - self.current_knob_dropdown.currentIndexChanged.connect( - lambda: self.loadKnobValue(False, updateDict=True)) - - # Layout - self.node_mode_bar_layout = QtWidgets.QHBoxLayout() - self.node_mode_bar_layout.addWidget(self.exit_node_btn) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_node_label_node) - self.node_mode_bar_layout.addWidget(self.current_node_label_name) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_knob_dropdown) - self.node_mode_bar = QtWidgets.QWidget() - self.node_mode_bar.setLayout(self.node_mode_bar_layout) - - self.node_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.2.B. Script mode UI - self.script_label = QtWidgets.QLabel("Script: ") - - self.current_folder_dropdown = QtWidgets.QComboBox() - self.current_folder_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.current_folder_dropdown.currentIndexChanged.connect( - self.folderDropdownChanged) - # self.current_folder_dropdown.setEditable(True) - # self.current_folder_dropdown.lineEdit().setReadOnly(True) - # self.current_folder_dropdown.lineEdit().setAlignment(Qt.AlignRight) - - self.current_script_dropdown = QtWidgets.QComboBox() - self.current_script_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateFoldersDropdown() - self.updateScriptsDropdown() - self.current_script_dropdown.currentIndexChanged.connect( - self.scriptDropdownChanged) - - # Layout - self.script_mode_bar_layout = QtWidgets.QHBoxLayout() - self.script_mode_bar_layout.addWidget(self.script_label) - self.script_mode_bar_layout.addSpacing(2) - self.script_mode_bar_layout.addWidget(self.current_folder_dropdown) - self.script_mode_bar_layout.addWidget(self.current_script_dropdown) - self.script_mode_bar = QtWidgets.QWidget() - self.script_mode_bar.setLayout(self.script_mode_bar_layout) - - self.script_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.3. File-system buttons - # Refresh dropdowns - self.refresh_btn = QtWidgets.QToolButton() - self.refresh_btn.setIcon(QtGui.QIcon(icons_path + "icon_refresh.png")) - self.refresh_btn.setIconSize(QtCore.QSize(50, 50)) - self.refresh_btn.setIconSize(self.qt_icon_size) - self.refresh_btn.setFixedSize(self.qt_btn_size) - self.refresh_btn.setToolTip("Refresh the dropdowns.\nShortcut: F5") - self.refresh_btn.setShortcut('F5') - self.refresh_btn.clicked.connect(self.refreshClicked) - - # Reload script - self.reload_btn = QtWidgets.QToolButton() - self.reload_btn.setIcon(QtGui.QIcon(icons_path + "icon_download.png")) - self.reload_btn.setIconSize(QtCore.QSize(50, 50)) - self.reload_btn.setIconSize(self.qt_icon_size) - self.reload_btn.setFixedSize(self.qt_btn_size) - self.reload_btn.setToolTip( - "Reload the current script. Will overwrite any changes made to it.\nShortcut: Ctrl+R") - self.reload_btn.setShortcut('Ctrl+R') - self.reload_btn.clicked.connect(self.reloadClicked) - - # Save script - self.save_btn = QtWidgets.QToolButton() - self.save_btn.setIcon(QtGui.QIcon(icons_path + "icon_save.png")) - self.save_btn.setIconSize(QtCore.QSize(50, 50)) - self.save_btn.setIconSize(self.qt_icon_size) - self.save_btn.setFixedSize(self.qt_btn_size) - self.save_btn.setToolTip( - "Save the script into the selected knob or python file.\nShortcut: Ctrl+S") - self.save_btn.setShortcut('Ctrl+S') - self.save_btn.clicked.connect(self.saveClicked) - - # Layout - self.top_file_bar_layout = QtWidgets.QHBoxLayout() - self.top_file_bar_layout.addWidget(self.refresh_btn) - self.top_file_bar_layout.addWidget(self.reload_btn) - self.top_file_bar_layout.addWidget(self.save_btn) - - # --- - # 2.4. Right Side buttons - - # Run script - self.run_script_button = QtWidgets.QToolButton() - self.run_script_button.setIcon( - QtGui.QIcon(icons_path + "icon_run.png")) - self.run_script_button.setIconSize(self.qt_icon_size) - # self.run_script_button.setIconSize(self.qt_icon_size) - self.run_script_button.setFixedSize(self.qt_btn_size) - self.run_script_button.setToolTip( - "Execute the current selection on the KnobScripter, or the whole script if no selection.\nShortcut: Ctrl+Enter") - self.run_script_button.clicked.connect(self.runScript) - - # Clear console - self.clear_console_button = QtWidgets.QToolButton() - self.clear_console_button.setIcon( - QtGui.QIcon(icons_path + "icon_clearConsole.png")) - self.clear_console_button.setIconSize(QtCore.QSize(50, 50)) - self.clear_console_button.setIconSize(self.qt_icon_size) - self.clear_console_button.setFixedSize(self.qt_btn_size) - self.clear_console_button.setToolTip( - "Clear the text in the console window.\nShortcut: Click Backspace on the console.") - self.clear_console_button.clicked.connect(self.clearConsole) - - # FindReplace button - self.find_button = QtWidgets.QToolButton() - self.find_button.setIcon(QtGui.QIcon(icons_path + "icon_search.png")) - self.find_button.setIconSize(self.qt_icon_size) - self.find_button.setFixedSize(self.qt_btn_size) - self.find_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.\nShortcut: Ctrl+F") - self.find_button.setShortcut('Ctrl+F') - #self.find_button.setMaximumWidth(self.find_button.fontMetrics().boundingRect("Find").width() + 20) - self.find_button.setCheckable(True) - self.find_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.find_button.clicked[bool].connect(self.toggleFRW) - if self.frw_open: - self.find_button.toggle() - - # Snippets - self.snippets_button = QtWidgets.QToolButton() - self.snippets_button.setIcon( - QtGui.QIcon(icons_path + "icon_snippets.png")) - self.snippets_button.setIconSize(QtCore.QSize(50, 50)) - self.snippets_button.setIconSize(self.qt_icon_size) - self.snippets_button.setFixedSize(self.qt_btn_size) - self.snippets_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.") - self.snippets_button.clicked.connect(self.openSnippets) - - # PIN - ''' - self.pin_button = QtWidgets.QPushButton("P") - self.pin_button.setCheckable(True) - if self.pinned: - self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.pin_button.toggle() - self.pin_button.setToolTip("Toggle 'Always On Top'. Keeps the KnobScripter on top of all other windows.") - self.pin_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.pin_button.setFixedSize(self.qt_btn_size) - self.pin_button.clicked[bool].connect(self.pin) - ''' - - # Prefs - self.createPrefsMenu() - self.prefs_button = QtWidgets.QPushButton() - self.prefs_button.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - self.prefs_button.setIconSize(self.qt_icon_size) - self.prefs_button.setFixedSize( - QtCore.QSize(self.btn_size + 10, self.btn_size)) - # self.prefs_button.clicked.connect(self.openPrefs) - self.prefs_button.setMenu(self.prefsMenu) - self.prefs_button.setStyleSheet("text-align:left;padding-left:2px;") - #self.prefs_button.setMaximumWidth(self.prefs_button.fontMetrics().boundingRect("Prefs").width() + 12) - - # Layout - self.top_right_bar_layout = QtWidgets.QHBoxLayout() - self.top_right_bar_layout.addWidget(self.run_script_button) - self.top_right_bar_layout.addWidget(self.clear_console_button) - self.top_right_bar_layout.addWidget(self.find_button) - # self.top_right_bar_layout.addWidget(self.snippets_button) - # self.top_right_bar_layout.addWidget(self.pin_button) - # self.top_right_bar_layout.addSpacing(10) - self.top_right_bar_layout.addWidget(self.prefs_button) - - # --- - # Layout - self.top_layout = QtWidgets.QHBoxLayout() - self.top_layout.setContentsMargins(0, 0, 0, 0) - # self.top_layout.setSpacing(10) - self.top_layout.addWidget(self.change_btn) - self.top_layout.addWidget(self.node_mode_bar) - self.top_layout.addWidget(self.script_mode_bar) - self.node_mode_bar.setVisible(False) - # self.top_layout.addSpacing(10) - self.top_layout.addLayout(self.top_file_bar_layout) - self.top_layout.addStretch() - self.top_layout.addLayout(self.top_right_bar_layout) - - # ---------------------- - # 3. SCRIPTING SECTION - # ---------------------- - # Splitter - self.splitter = QtWidgets.QSplitter(Qt.Vertical) - - # Output widget - self.script_output = ScriptOutputWidget(parent=self) - self.script_output.setReadOnly(1) - self.script_output.setAcceptRichText(0) - self.script_output.setTabStopWidth( - self.script_output.tabStopWidth() / 4) - self.script_output.setFocusPolicy(Qt.ClickFocus) - self.script_output.setAutoFillBackground(0) - self.script_output.installEventFilter(self) - - # Script Editor - self.script_editor = KnobScripterTextEditMain(self, self.script_output) - self.script_editor.setMinimumHeight(30) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor.textChanged.connect(self.setModified) - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor.cursorPositionChanged.connect(self.setTextSelection) - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily(self.font) - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(self.fontSize) - self.script_editor.setFont(self.script_editor_font) - self.script_editor.setTabStopWidth( - self.tabSpaces * QtGui.QFontMetrics(self.script_editor_font).width(' ')) - - # Add input and output to splitter - self.splitter.addWidget(self.script_output) - self.splitter.addWidget(self.script_editor) - self.splitter.setStretchFactor(0, 0) - - # FindReplace widget - self.frw = FindReplaceWidget(self) - self.frw.setVisible(self.frw_open) - - # --- - # Layout - self.scripting_layout = QtWidgets.QVBoxLayout() - self.scripting_layout.setContentsMargins(0, 0, 0, 0) - self.scripting_layout.setSpacing(0) - self.scripting_layout.addWidget(self.splitter) - self.scripting_layout.addWidget(self.frw) - - # --------------- - # MASTER LAYOUT - # --------------- - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.setSpacing(5) - self.master_layout.setContentsMargins(8, 8, 8, 8) - self.master_layout.addLayout(self.top_layout) - self.master_layout.addLayout(self.scripting_layout) - # self.master_layout.addLayout(self.bottom_layout) - self.setLayout(self.master_layout) - - # ---------------- - # MAIN WINDOW UI - # ---------------- - size_policy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - self.setSizePolicy(size_policy) - self.setMinimumWidth(160) - - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - - # Set default values based on mode - if self.nodeMode: - self.current_knob_dropdown.blockSignals(True) - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - self.setCurrentKnob(self.knob) - self.loadKnobValue(check=False) - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - self.splitter.setSizes([0, 1]) - else: - self.exitNodeMode() - self.script_editor.setFocus() - - # Preferences submenus - def createPrefsMenu(self): - - # Actions - self.echoAct = QtWidgets.QAction("Echo python commands", self, checkable=True, - statusTip="Toggle nuke's 'Echo all python commands to ScriptEditor'", triggered=self.toggleEcho) - if nuke.toNode("preferences").knob("echoAllCommands").value(): - self.echoAct.toggle() - self.pinAct = QtWidgets.QAction("Always on top", self, checkable=True, - statusTip="Keeps the KnobScripter window always on top or not.", triggered=self.togglePin) - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinAct.toggle() - self.helpAct = QtWidgets.QAction( - "&Help", self, statusTip="Open the KnobScripter help in your browser.", shortcut="F1", triggered=self.showHelp) - self.nukepediaAct = QtWidgets.QAction( - "Show in Nukepedia", self, statusTip="Open the KnobScripter download page on Nukepedia.", triggered=self.showInNukepedia) - self.githubAct = QtWidgets.QAction( - "Show in GitHub", self, statusTip="Open the KnobScripter repo on GitHub.", triggered=self.showInGithub) - self.snippetsAct = QtWidgets.QAction( - "Snippets", self, statusTip="Open the Snippets editor.", triggered=self.openSnippets) - self.snippetsAct.setIcon(QtGui.QIcon(icons_path + "icon_snippets.png")) - # self.snippetsAct = QtWidgets.QAction("Keywords", self, statusTip="Add custom keywords.", triggered=self.openSnippets) #TODO THIS - self.prefsAct = QtWidgets.QAction( - "Preferences", self, statusTip="Open the Preferences panel.", triggered=self.openPrefs) - self.prefsAct.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - - # Menus - self.prefsMenu = QtWidgets.QMenu("Preferences") - self.prefsMenu.addAction(self.echoAct) - self.prefsMenu.addAction(self.pinAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.nukepediaAct) - self.prefsMenu.addAction(self.githubAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.helpAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.snippetsAct) - self.prefsMenu.addAction(self.prefsAct) - - def initEcho(self): - ''' Initializes the echo chechable QAction based on nuke's state ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - self.echoAct.setChecked(echo_knob.value()) - - def toggleEcho(self): - ''' Toggle the "Echo python commands" from Nuke ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - echo_knob.setValue(self.echoAct.isChecked()) - - def togglePin(self): - ''' Toggle "always on top" based on the submenu button ''' - self.pin(self.pinAct.isChecked()) - - def showInNukepedia(self): - openUrl("http://www.nukepedia.com/python/ui/knobscripter") - - def showInGithub(self): - openUrl("https://github.com/adrianpueyo/KnobScripter") - - def showHelp(self): - openUrl("https://vimeo.com/adrianpueyo/knobscripter2") - - # Node Mode - - def updateKnobDropdown(self): - ''' Populate knob dropdown list ''' - self.current_knob_dropdown.clear() # First remove all items - defaultKnobs = ["knobChanged", "onCreate", "onScriptLoad", "onScriptSave", "onScriptClose", "onDestroy", - "updateUI", "autolabel", "beforeRender", "beforeFrameRender", "afterFrameRender", "afterRender"] - permittedKnobClasses = ["PyScript_Knob", "PythonCustomKnob"] - counter = 0 - for i in self.node.knobs(): - if i not in defaultKnobs and self.node.knob(i).Class() in permittedKnobClasses: - if self.show_labels: - i_full = "{} ({})".format(self.node.knob(i).label(), i) - else: - i_full = i - - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i_full + "(*)", i) - else: - self.current_knob_dropdown.addItem(i_full, i) - - counter += 1 - if counter > 0: - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - for i in self.node.knobs(): - if i in defaultKnobs: - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i + "(*)", i) - else: - self.current_knob_dropdown.addItem(i, i) - counter += 1 - return - - def loadKnobValue(self, check=True, updateDict=False): - ''' Get the content of the knob value and populate the editor ''' - if self.toLoadKnob == False: - return - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - obtained_scrollValue = 0 - edited_knobValue = self.script_editor.toPlainText() - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - # If there were changes to the previous knob, update the dictionary - if updateDict == True: - self.unsavedKnobs[self.knob] = edited_knobValue - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - prev_knob = self.knob # knobChanged... - - self.knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The Script Editor has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - self.setCurrentKnob(prev_knob) - return - # If order comes from a dropdown update, update value from dictionary if possible, otherwise update normally - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.name(), self.knob)) - if updateDict: - if self.knob in self.unsavedKnobs: - if self.unsavedKnobs[self.knob] == obtained_knobValue: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - else: - obtained_knobValue = self.unsavedKnobs[self.knob] - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(True) - else: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - - if self.knob in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.knob] - else: - self.script_editor.setPlainText(obtained_knobValue) - - cursor = self.script_editor.textCursor() - self.script_editor.setTextCursor(cursor) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - return - - def loadAllKnobValues(self): - ''' Load all knobs button's function ''' - if len(self.unsavedKnobs) >= 1: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to reload all python and callback knobs?") - msgBox.setInformativeText( - "Unsaved changes on this editor will be lost.") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.unsavedKnobs = {} - return - - def saveKnobValue(self, check=True): - ''' Save the text from the editor to the node's knobChanged knob ''' - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - self.knob = dropdown_value - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - edited_knobValue = self.script_editor.toPlainText() - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Do you want to overwrite %s.%s?" % - (self.node.name(), dropdown_value)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.node[dropdown_value].setValue(edited_knobValue) - self.setKnobModified( - modified=False, knob=dropdown_value, changeTitle=True) - nuke.tcl("modified 1") - if self.knob in self.unsavedKnobs: - del self.unsavedKnobs[self.knob] - return - - def saveAllKnobValues(self, check=True): - ''' Save all knobs button's function ''' - if self.updateUnsavedKnobs() > 0 and check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to save all modified python and callback knobs?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - saveErrors = 0 - savedCount = 0 - for k in self.unsavedKnobs.copy(): - try: - self.node.knob(k).setValue(self.unsavedKnobs[k]) - del self.unsavedKnobs[k] - savedCount += 1 - nuke.tcl("modified 1") - except: - saveErrors += 1 - if saveErrors > 0: - errorBox = QtWidgets.QMessageBox() - errorBox.setText("Error saving %s knob%s." % - (str(saveErrors), int(saveErrors > 1) * "s")) - errorBox.setIcon(QtWidgets.QMessageBox.Warning) - errorBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - errorBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = errorBox.exec_() - else: - log("KnobScripter: %s knobs saved" % str(savedCount)) - return - - def setCurrentKnob(self, knobToSet): - ''' Set current knob ''' - KnobDropdownItems = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - KnobDropdownItems.append( - self.current_knob_dropdown.itemData(i)) - else: - KnobDropdownItems.append("---") - if knobToSet in KnobDropdownItems: - index = KnobDropdownItems.index(knobToSet) - self.current_knob_dropdown.setCurrentIndex(index) - return - - def updateUnsavedKnobs(self, first_time=False): - ''' Clear unchanged knobs from the dict and return the number of unsaved knobs ''' - if not self.node: - # Node has been deleted, so simply return 0. Who cares. - return 0 - edited_knobValue = self.script_editor.toPlainText() - self.unsavedKnobs[self.knob] = edited_knobValue - if len(self.unsavedKnobs) > 0: - for k in self.unsavedKnobs.copy(): - if self.node.knob(k): - if str(self.node.knob(k).value()) == str(self.unsavedKnobs[k]): - del self.unsavedKnobs[k] - else: - del self.unsavedKnobs[k] - # Set appropriate knobs modified... - knobs_dropdown = self.current_knob_dropdown - all_knobs = [knobs_dropdown.itemData(i) - for i in range(knobs_dropdown.count())] - for key in all_knobs: - if key in self.unsavedKnobs.keys(): - self.setKnobModified( - modified=True, knob=key, changeTitle=False) - else: - self.setKnobModified( - modified=False, knob=key, changeTitle=False) - - return len(self.unsavedKnobs) - - def setKnobModified(self, modified=True, knob="", changeTitle=True): - ''' Sets the current knob modified, title and whatever else we need ''' - if knob == "": - knob = self.knob - if modified: - self.modifiedKnobs.add(knob) - else: - self.modifiedKnobs.discard(knob) - - if changeTitle: - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - - try: - knobs_dropdown = self.current_knob_dropdown - kd_index = knobs_dropdown.currentIndex() - kd_data = knobs_dropdown.itemData(kd_index) - if self.show_labels and i not in defaultKnobs: - kd_data = "{} ({})".format( - self.node.knob(kd_data).label(), kd_data) - if modified == False: - knobs_dropdown.setItemText(kd_index, kd_data) - else: - knobs_dropdown.setItemText(kd_index, kd_data + "(*)") - except: - pass - - # Script Mode - def updateFoldersDropdown(self): - ''' Populate folders dropdown list ''' - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.clear() # First remove all items - defaultFolders = ["scripts"] - scriptFolders = [] - counter = 0 - for f in defaultFolders: - self.makeScriptFolder(f) - self.current_folder_dropdown.addItem(f + "/", f) - counter += 1 - - try: - scriptFolders = sorted([f for f in os.listdir(self.scripts_dir) if os.path.isdir( - os.path.join(self.scripts_dir, f))]) # Accepts symlinks!!! - except: - log("Couldn't read any script folders.") - - for f in scriptFolders: - fname = f.split("/")[-1] - if fname in defaultFolders: - continue - self.current_folder_dropdown.addItem(fname + "/", fname) - counter += 1 - - # print scriptFolders - if counter > 0: - self.current_folder_dropdown.insertSeparator(counter) - counter += 1 - # self.current_folder_dropdown.insertSeparator(counter) - #counter += 1 - self.current_folder_dropdown.addItem("New", "create new") - self.current_folder_dropdown.addItem("Open...", "open in browser") - self.current_folder_dropdown.addItem("Add custom", "add custom path") - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - def updateScriptsDropdown(self): - ''' Populate py scripts dropdown list ''' - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.clear() # First remove all items - QtWidgets.QApplication.processEvents() - log("# Updating scripts dropdown...") - log("scripts dir:" + self.scripts_dir) - log("current folder:" + self.current_folder) - log("previous current script:" + self.current_script) - #current_folder = self.current_folder_dropdown.itemData(self.current_folder_dropdown.currentIndex()) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - defaultScripts = ["Untitled.py"] - found_scripts = [] - counter = 0 - # All files and folders inside of the folder - dir_list = os.listdir(current_folder_path) - try: - found_scripts = sorted([f for f in dir_list if f.endswith(".py")]) - found_temp_scripts = [ - f for f in dir_list if f.endswith(".py.autosave")] - except: - log("Couldn't find any scripts in the selected folder.") - if not len(found_scripts): - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - else: - self.current_script_dropdown.addItem(s, s) - counter += 1 - else: - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - elif s in found_scripts: - self.current_script_dropdown.addItem(s, s) - for s in found_scripts: - if s in defaultScripts: - continue - sname = s.split("/")[-1] - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(sname + "(*)", sname) - else: - self.current_script_dropdown.addItem(sname, sname) - counter += 1 - # else: #Add the found scripts to the dropdown - if counter > 0: - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - self.current_script_dropdown.addItem("New", "create new") - self.current_script_dropdown.addItem("Duplicate", "create duplicate") - self.current_script_dropdown.addItem("Delete", "delete script") - self.current_script_dropdown.addItem("Open", "open in browser") - #self.script_index = self.current_script_dropdown.currentIndex() - self.script_index = 0 - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - log("Finished updating scripts dropdown.") - log("current_script:" + self.current_script) - self.current_script_dropdown.blockSignals(False) - return - - def makeScriptFolder(self, name="scripts"): - folder_path = os.path.join(self.scripts_dir, name) - if not os.path.exists(folder_path): - try: - os.makedirs(folder_path) - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def makeScriptFile(self, name="Untitled.py", folder="scripts", empty=True): - script_path = os.path.join(self.scripts_dir, self.current_folder, name) - if not os.path.isfile(script_path): - try: - self.current_script_file = open(script_path, 'w') - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def setCurrentFolder(self, folderName): - ''' Set current folder ON THE DROPDOWN ONLY''' - folderList = [self.current_folder_dropdown.itemData( - i) for i in range(self.current_folder_dropdown.count())] - if folderName in folderList: - index = folderList.index(folderName) - self.current_folder_dropdown.setCurrentIndex(index) - self.current_folder = folderName - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - return - - def setCurrentScript(self, scriptName): - ''' Set current script ON THE DROPDOWN ONLY ''' - scriptList = [self.current_script_dropdown.itemData( - i) for i in range(self.current_script_dropdown.count())] - if scriptName in scriptList: - index = scriptList.index(scriptName) - self.current_script_dropdown.setCurrentIndex(index) - self.current_script = scriptName - self.script_index = self.current_script_dropdown.currentIndex() - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - return - - def loadScriptContents(self, check=False, pyOnly=False, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to load script contents now.") - obtained_scrollValue = 0 - obtained_cursorPosValue = [0, 0] # Position, anchor - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if (self.current_folder + "/" + self.current_script) in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.current_folder + - "/" + self.current_script] - if (self.current_folder + "/" + self.current_script) in self.cursorPos: - obtained_cursorPosValue = self.cursorPos[self.current_folder + - "/" + self.current_script] - - # 1: If autosave exists and pyOnly is false, load it - if os.path.isfile(script_path_temp) and not pyOnly: - log("Loading .py.autosave file\n---") - with open(script_path_temp, 'r') as script: - content = script.read() - self.script_editor.setPlainText(content) - self.setScriptModified(True) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - - # 2: Try to load the .py as first priority, if it exists - elif os.path.isfile(script_path): - log("Loading .py file\n---") - with open(script_path, 'r') as script: - content = script.read() - current_text = self.script_editor.toPlainText().encode("utf8") - if check and current_text != content and current_text.strip() != "": - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The script has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.script_editor.setPlainText(content) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - self.setScriptModified(False) - self.loadScriptState() - self.setScriptState() - - # 3: If .py doesn't exist... only then stick to the autosave - elif os.path.isfile(script_path_temp): - with open(script_path_temp, 'r') as script: - content = script.read() - - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The .py file hasn't been found.") - msgBox.setInformativeText( - "Do you want to clear the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - - # Clear trash - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.script_editor.setPlainText("") - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.loadScriptState() - self.setScriptState() - - else: - content = "" - self.script_editor.setPlainText(content) - self.setScriptModified(False) - if self.current_folder + "/" + self.current_script in self.scrollPos: - del self.scrollPos[self.current_folder + - "/" + self.current_script] - if self.current_folder + "/" + self.current_script in self.cursorPos: - del self.cursorPos[self.current_folder + - "/" + self.current_script] - - self.setWindowTitle("KnobScripter - %s/%s" % - (self.current_folder, self.current_script)) - return - - def saveScriptContents(self, temp=True): - ''' Save the current contents of the editor into the python file. If temp == True, saves a .py.autosave file ''' - log("\n# About to save script contents now.") - log("Temp mode is: " + str(temp)) - log("self.current_folder: " + self.current_folder) - log("self.current_script: " + self.current_script) - script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - script_path_temp = script_path + ".autosave" - orig_content = "" - content = self.script_editor.toPlainText().encode('utf8') - - if temp == True: - if os.path.isfile(script_path): - with open(script_path, 'r') as script: - orig_content = script.read() - # If script path doesn't exist and autosave does but the script is empty... - elif content == "" and os.path.isfile(script_path_temp): - os.remove(script_path_temp) - return - if content != orig_content: - with open(script_path_temp, 'w') as script: - script.write(content) - else: - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Nothing to save") - return - else: - with open(script_path, 'w') as script: - script.write(self.script_editor.toPlainText().encode('utf8')) - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.saveScrollValue() - self.saveCursorPosValue() - log("Saved " + script_path + "\n---") - return - - def deleteScript(self, check=True, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to delete the .py and/or autosave script now.") - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("You're about to delete this script.") - msgBox.setInformativeText( - "Are you sure you want to delete {}?".format(self.current_script)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.No) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return False - - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - - if os.path.isfile(script_path): - os.remove(script_path) - log("Removed " + script_path) - - return True - - def folderDropdownChanged(self): - '''Executed when the current folder dropdown is changed''' - self.saveScriptState() - log("# folder dropdown changed") - folders_dropdown = self.current_folder_dropdown - fd_value = folders_dropdown.currentText() - fd_index = folders_dropdown.currentIndex() - fd_data = folders_dropdown.itemData(fd_index) - if fd_data == "create new": - panel = FileNameDialog(self, mode="folder") - # panel.setWidth(260) - # panel.addSingleLineInput("Name:","") - if panel.exec_(): - # Accepted - folder_name = panel.text - if os.path.isdir(os.path.join(self.scripts_dir, folder_name)): - self.messageBox("Folder already exists.") - self.setCurrentFolder(self.current_folder) - if self.makeScriptFolder(name=folder_name): - self.saveScriptContents(temp=True) - # Success creating the folder - self.current_folder = folder_name - self.updateFoldersDropdown() - self.setCurrentFolder(folder_name) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - else: - self.messageBox("There was a problem creating the folder.") - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # Canceled/rejected - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "open in browser": - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - self.openInFileBrowser(current_folder_path) - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "add custom path": - folder_path = nuke.getFilename('Select custom folder.') - if folder_path is not None: - if folder_path.endswith("/"): - aliasName = folder_path.split("/")[-2] - else: - aliasName = folder_path.split("/")[-1] - if not os.path.isdir(folder_path): - self.messageBox( - "Folder not found. Please try again with the full path to a folder.") - elif not len(aliasName): - self.messageBox( - "Folder with the same name already exists. Please delete or rename it first.") - else: - # All good - os.symlink(folder_path, os.path.join( - self.scripts_dir, aliasName)) - self.saveScriptContents(temp=True) - self.current_folder = aliasName - self.updateFoldersDropdown() - self.setCurrentFolder(aliasName) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.script_editor.setFocus() - return - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # 1: Save current script as temp if needed - self.saveScriptContents(temp=True) - # 2: Set the new folder in the variables - self.current_folder = fd_data - self.folder_index = fd_index - # 3: Update the scripts dropdown - self.updateScriptsDropdown() - # 4: Load the current script! - self.loadScriptContents() - self.script_editor.setFocus() - - self.loadScriptState() - self.setScriptState() - - return - - def scriptDropdownChanged(self): - '''Executed when the current script dropdown is changed. Should only be called by the manual dropdown change. Not by other functions.''' - self.saveScriptState() - scripts_dropdown = self.current_script_dropdown - sd_value = scripts_dropdown.currentText() - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if sd_data == "create new": - self.current_script_dropdown.blockSignals(True) - panel = FileNameDialog(self, mode="script") - if panel.exec_(): - # Accepted - script_name = panel.text + ".py" - script_path = os.path.join( - self.scripts_dir, self.current_folder, script_name) - log(script_name) - log(script_path) - if os.path.isfile(script_path): - self.messageBox("Script already exists.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - if self.current_script != "Untitled.py": - self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.saveScriptContents(temp=False) - # self.loadScriptContents() - else: - self.messageBox("There was a problem creating the script.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - else: - # Canceled/rejected - self.current_script_dropdown.setCurrentIndex(self.script_index) - return - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "create duplicate": - self.current_script_dropdown.blockSignals(True) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - - current_name = self.current_script - if self.current_script.endswith(".py"): - current_name = current_name[:-3] - - test_name = current_name - while True: - test_name += "_copy" - new_script_path = os.path.join( - self.scripts_dir, self.current_folder, test_name + ".py") - if not os.path.isfile(new_script_path): - break - - script_name = test_name + ".py" - - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - # self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.script_editor.setFocus() - else: - self.messageBox("There was a problem duplicating the script.") - self.current_script_dropdown.setCurrentIndex(self.script_index) - - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "open in browser": - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - self.openInFileBrowser(current_script_path) - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - return - - elif sd_data == "delete script": - if self.deleteScript(): - self.updateScriptsDropdown() - self.loadScriptContents() - else: - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - - else: - self.saveScriptContents() - self.current_script = sd_data - self.script_index = sd_index - self.setCurrentScript(self.current_script) - self.loadScriptContents() - self.script_editor.setFocus() - self.loadScriptState() - self.setScriptState() - return - - def setScriptModified(self, modified=True): - ''' Sets self.current_script_modified, title and whatever else we need ''' - self.current_script_modified = modified - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - try: - scripts_dropdown = self.current_script_dropdown - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if modified == False: - scripts_dropdown.setItemText(sd_index, sd_data) - else: - scripts_dropdown.setItemText(sd_index, sd_data + "(*)") - except: - pass - - def openInFileBrowser(self, path=""): - OS = platform.system() - if not os.path.exists(path): - path = KS_DIR - if OS == "Windows": - os.startfile(path) - elif OS == "Darwin": - subprocess.Popen(["open", path]) - else: - subprocess.Popen(["xdg-open", path]) - - def loadScriptState(self): - ''' - Loads the last state of the script from a file inside the SE directory's root. - SAVES self.scroll_pos, self.cursor_pos, self.last_open_script - ''' - self.state_dict = {} - if not os.path.isfile(self.state_txt_path): - return False - else: - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - log("Loading script state into self.state_dict, self.scrollPos, self.cursorPos") - log(self.state_dict) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - def setScriptState(self): - ''' - Sets the already script state from self.state_dict into the current script if applicable - ''' - script_fullname = self.current_folder + "/" + self.current_script - - if "scroll_pos" in self.state_dict: - if script_fullname in self.state_dict["scroll_pos"]: - self.script_editor.verticalScrollBar().setValue( - int(self.state_dict["scroll_pos"][script_fullname])) - - if "cursor_pos" in self.state_dict: - if script_fullname in self.state_dict["cursor_pos"]: - cursor = self.script_editor.textCursor() - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][1]), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][0]), QtGui.QTextCursor.KeepAnchor) - self.script_editor.setTextCursor(cursor) - - if 'splitter_sizes' in self.state_dict: - self.splitter.setSizes(self.state_dict['splitter_sizes']) - - def setLastScript(self): - if 'last_folder' in self.state_dict and 'last_script' in self.state_dict: - self.updateFoldersDropdown() - self.setCurrentFolder(self.state_dict['last_folder']) - self.updateScriptsDropdown() - self.setCurrentScript(self.state_dict['last_script']) - self.loadScriptContents() - self.script_editor.setFocus() - - def saveScriptState(self): - ''' Stores the current state of the script into a file inside the SE directory's root ''' - log("About to save script state...") - ''' - # self.state_dict = {} - if os.path.isfile(self.state_txt_path): - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - ''' - self.loadScriptState() - - # Overwrite current values into the scriptState - self.saveScrollValue() - self.saveCursorPosValue() - - self.state_dict['scroll_pos'] = self.scrollPos - self.state_dict['cursor_pos'] = self.cursorPos - self.state_dict['last_folder'] = self.current_folder - self.state_dict['last_script'] = self.current_script - self.state_dict['splitter_sizes'] = self.splitter.sizes() - - with open(self.state_txt_path, "w") as f: - state = json.dump(self.state_dict, f, sort_keys=True, indent=4) - return state - - # Autosave background loop - def autosave(self): - if self.toAutosave: - # Save the script... - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - log("autosaving...") - return - - # Global stuff - def setTextSelection(self): - self.highlighter.selected_text = self.script_editor.textCursor().selection().toPlainText() - return - - def eventFilter(self, object, event): - if event.type() == QtCore.QEvent.KeyPress: - return QtWidgets.QWidget.eventFilter(self, object, event) - else: - return QtWidgets.QWidget.eventFilter(self, object, event) - - def resizeEvent(self, res_event): - w = self.frameGeometry().width() - self.current_node_label_node.setVisible(w > 460) - self.script_label.setVisible(w > 460) - return super(KnobScripter, self).resizeEvent(res_event) - - def changeClicked(self, newNode=""): - ''' Change node ''' - try: - print "Changing from " + self.node.name() - except: - self.node = None - if not len(nuke.selectedNodes()): - self.exitNodeMode() - return - nuke.menu("Nuke").findItem( - "Edit/Node/Update KnobScripter Context").invoke() - selection = knobScripterSelectedNodes - if self.nodeMode: # Only update the number of unsaved knobs if we were already in node mode - if self.node is not None: - updatedCount = self.updateUnsavedKnobs() - else: - updatedCount = 0 - else: - updatedCount = 0 - self.autosave() - if newNode != "" and nuke.exists(newNode): - selection = [newNode] - elif not len(selection): - node_dialog = ChooseNodeDialog(self) - if node_dialog.exec_(): - # Accepted - selection = [nuke.toNode(node_dialog.name)] - else: - return - - # Change to node mode... - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - if not self.nodeMode: - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - self.splitter.setSizes([0, 1]) - self.nodeMode = True - - # If already selected, pass - if self.node is not None and selection[0].fullName() == self.node.fullName(): - self.messageBox("Please select a different node first!") - return - elif updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Save changes to %s knob%s before changing the node?" % (str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - elif reply == QtWidgets.QMessageBox.Cancel: - return - if len(selection) > 1: - self.messageBox( - "More than one node selected.\nChanging knobChanged editor to %s" % selection[0].fullName()) - # Reinitialise everything, wooo! - self.current_knob_dropdown.blockSignals(True) - self.node = selection[0] - - self.script_editor.setPlainText("") - self.unsavedKnobs = {} - self.scrollPos = {} - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.current_node_label_name.setText(self.node.fullName()) - - self.toLoadKnob = False - self.updateKnobDropdown() # onee - # self.current_knob_dropdown.repaint() - # self.current_knob_dropdown.setMinimumWidth(self.current_knob_dropdown.minimumSizeHint().width()) - self.toLoadKnob = True - self.setCurrentKnob(self.knob) - self.loadKnobValue(False) - self.script_editor.setFocus() - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - # self.current_knob_dropdown.setMinimumContentsLength(80) - return - - def exitNodeMode(self): - self.nodeMode = False - self.setWindowTitle("KnobScripter - Script Mode") - self.node_mode_bar.setVisible(False) - self.script_mode_bar.setVisible(True) - self.node = nuke.toNode("root") - # self.updateFoldersDropdown() - # self.updateScriptsDropdown() - self.splitter.setSizes([1, 1]) - self.loadScriptState() - self.setLastScript() - - self.loadScriptContents(check=False) - self.setScriptState() - - def clearConsole(self): - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - self.script_output.setPlainText("") - - def toggleFRW(self, frw_pressed): - self.frw_open = frw_pressed - self.frw.setVisible(self.frw_open) - if self.frw_open: - self.frw.find_lineEdit.setFocus() - self.frw.find_lineEdit.selectAll() - else: - self.script_editor.setFocus() - return - - def openSnippets(self): - ''' Whenever the 'snippets' button is pressed... open the panel ''' - global SnippetEditPanel - if SnippetEditPanel == "": - SnippetEditPanel = SnippetsPanel(self) - - if not SnippetEditPanel.isVisible(): - SnippetEditPanel.reload() - - if SnippetEditPanel.show(): - self.snippets = self.loadSnippets(maxDepth=5) - SnippetEditPanel = "" - - def loadSnippets(self, path="", maxDepth=5, depth=0): - ''' - Load prefs recursive. When maximum recursion depth, ignores paths. - ''' - max_depth = maxDepth - cur_depth = depth - if path == "": - path = self.snippets_txt_path - if not os.path.isfile(path): - return {} - else: - loaded_snippets = {} - with open(path, "r") as f: - file = json.load(f) - for i, (key, val) in enumerate(file.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - if cur_depth < max_depth: - new_dict = self.loadSnippets( - path=val, maxDepth=max_depth, depth=cur_depth + 1) - loaded_snippets.update(new_dict) - else: - loaded_snippets[key] = val - return loaded_snippets - - def messageBox(self, the_text=""): - ''' Just a simple message box ''' - if self.isPane: - msgBox = QtWidgets.QMessageBox() - else: - msgBox = QtWidgets.QMessageBox(self) - msgBox.setText(the_text) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.exec_() - - def openPrefs(self): - ''' Open the preferences panel ''' - global PrefsPanel - if PrefsPanel == "": - PrefsPanel = KnobScripterPrefs(self) - - if PrefsPanel.show(): - PrefsPanel = "" - - def loadPrefs(self): - ''' Load prefs ''' - if not os.path.isfile(self.prefs_txt): - return [] - else: - with open(self.prefs_txt, "r") as f: - prefs = json.load(f) - return prefs - - def runScript(self): - ''' Run the current script... ''' - self.script_editor.runScript() - - def saveScrollValue(self): - ''' Save scroll values ''' - if self.nodeMode: - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - else: - self.scrollPos[self.current_folder + "/" + - self.current_script] = self.script_editor.verticalScrollBar().value() - - def saveCursorPosValue(self): - ''' Save cursor pos and anchor values ''' - self.cursorPos[self.current_folder + "/" + self.current_script] = [ - self.script_editor.textCursor().position(), self.script_editor.textCursor().anchor()] - - def closeEvent(self, close_event): - if self.nodeMode: - updatedCount = self.updateUnsavedKnobs() - if updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Save changes to %s knob%s before closing?" % ( - str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - close_event.accept() - return - elif reply == QtWidgets.QMessageBox.Cancel: - close_event.ignore() - return - else: - close_event.accept() - else: - self.autosave() - if self in AllKnobScripters: - AllKnobScripters.remove(self) - close_event.accept() - - # Landing functions - - def refreshClicked(self): - ''' Function to refresh the dropdowns ''' - if self.nodeMode: - knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()).encode('UTF8') - self.current_knob_dropdown.blockSignals(True) - self.current_knob_dropdown.clear() # First remove all items - self.updateKnobDropdown() - availableKnobs = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - availableKnobs.append( - self.current_knob_dropdown.itemData(i).encode('UTF8')) - if knob in availableKnobs: - self.setCurrentKnob(knob) - self.current_knob_dropdown.blockSignals(False) - else: - folder = self.current_folder - script = self.current_script - self.autosave() - self.updateFoldersDropdown() - self.setCurrentFolder(folder) - self.updateScriptsDropdown() - self.setCurrentScript(script) - self.script_editor.setFocus() - - def reloadClicked(self): - if self.nodeMode: - self.loadKnobValue() - else: - log("Node mode is off") - self.loadScriptContents(check=True, pyOnly=True) - - def saveClicked(self): - if self.nodeMode: - self.saveKnobValue(False) - else: - self.saveScriptContents(temp=False) - - def setModified(self): - if self.nodeMode: - self.setKnobModified(True) - elif not self.current_script_modified: - self.setScriptModified(True) - if not self.nodeMode: - self.toAutosave = True - - def pin(self, pressed): - if pressed: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = True - self.show() - else: - self.setWindowFlags(self.windowFlags() & ~ - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = False - self.show() - - def findSE(self): - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle(): - return widget - - # FunctiosaveScrollValuens for Nuke's Script Editor - def findScriptEditors(self): - script_editors = [] - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle() and len(widget.children()) > 5: - script_editors.append(widget) - return script_editors - - def findSEInput(self, se): - return se.children()[-1].children()[0] - - def findSEOutput(self, se): - return se.children()[-1].children()[1] - - def findSERunBtn(self, se): - for btn in se.children(): - try: - if "Run the current script" in btn.toolTip(): - return btn - except: - pass - return False - - def setSEOutputEvent(self): - nukeScriptEditors = self.findScriptEditors() - # Take the console from the first script editor found... - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - for se in nukeScriptEditors: - se_output = self.findSEOutput(se) - se_output.textChanged.connect( - partial(consoleChanged, se_output, self)) - consoleChanged(se_output, self) # Initialise. - - -class KnobScripterPane(KnobScripter): - def __init__(self, node="", knob="knobChanged"): - super(KnobScripterPane, self).__init__() - self.isPane = True - - def showEvent(self, the_event): - try: - killPaneMargins(self) - except: - pass - return KnobScripter.showEvent(self, the_event) - - def hideEvent(self, the_event): - self.autosave() - return KnobScripter.hideEvent(self, the_event) - - -def consoleChanged(self, ks): - ''' This will be called every time the ScriptEditor Output text is changed ''' - try: - if ks: # KS exists - ksOutput = ks.script_output # The console TextEdit widget - ksText = self.document().toPlainText().encode("utf8") - # The text from the console that will be omitted - origConsoleText = ks.origConsoleText - if ksText.startswith(origConsoleText): - ksText = ksText[len(origConsoleText):] - else: - ks.origConsoleText = "" - ksOutput.setPlainText(ksText) - ksOutput.verticalScrollBar().setValue(ksOutput.verticalScrollBar().maximum()) - except: - pass - - -def killPaneMargins(widget_object): - if widget_object: - target_widgets = set() - target_widgets.add(widget_object.parentWidget().parentWidget()) - target_widgets.add(widget_object.parentWidget( - ).parentWidget().parentWidget().parentWidget()) - - for widget_layout in target_widgets: - try: - widget_layout.layout().setContentsMargins(0, 0, 0, 0) - except: - pass - - -def debug(lev=0): - ''' Convenience function to set the KnobScripter on debug mode''' - # levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] - # for handler in logging.root.handlers[:]: - # logging.root.removeHandler(handler) - # logging.basicConfig(level=levels[lev]) - # Changed to a shitty way for now - global DebugMode - DebugMode = True - - -def log(text): - ''' Display a debug info message. Yes, in a stupid way. I know.''' - global DebugMode - if DebugMode: - print(text) - - -# --------------------------------------------------------------------- -# Dialogs -# --------------------------------------------------------------------- -class FileNameDialog(QtWidgets.QDialog): - ''' - Dialog for creating new... (mode = "folder", "script" or "knob"). - ''' - - def __init__(self, parent=None, mode="folder", text=""): - if parent.isPane: - super(FileNameDialog, self).__init__() - else: - super(FileNameDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.mode = mode - self.text = text - - title = "Create new {}.".format(self.mode) - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - txt = self.name_lineEdit.text() - m = r"[\w]*$" - if self.mode == "knob": # Knobs can't start with a number... - m = r"[a-zA-Z_]+" + m - - if re.match(m, txt) or txt == "": - self.text = txt - else: - self.name_lineEdit.setText(self.text) - - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - return - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class TextInputDialog(QtWidgets.QDialog): - ''' - Simple dialog for a text input. - ''' - - def __init__(self, parent=None, name="", text="", title=""): - if parent.isPane: - super(TextInputDialog, self).__init__() - else: - super(TextInputDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - - self.name = name # title of textinput - self.text = text # default content of textinput - - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel(self.name + ": ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - #self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - self.text = self.name_lineEdit.text() - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class ChooseNodeDialog(QtWidgets.QDialog): - ''' - Dialog for selecting a node by its name. Only admits nodes that exist (including root, preferences...) - ''' - - def __init__(self, parent=None, name=""): - if parent.isPane: - super(ChooseNodeDialog, self).__init__() - else: - super(ChooseNodeDialog, self).__init__(parent) - - self.name = name # Name of node (will be "" by default) - self.allNodes = [] - - self.setWindowTitle("Enter the node's name...") - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.name) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - self.allNodes = self.getAllNodes() - completer = QtWidgets.QCompleter(self.allNodes, self) - completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.name_lineEdit.setCompleter(completer) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - nuke.exists(self.name)) - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def getAllNodes(self): - self.allNodes = [n.fullName() for n in nuke.allNodes( - recurseGroups=True)] # if parent is in current context?? - self.allNodes.extend(["root", "preferences"]) - return self.allNodes - - def nameChanged(self): - self.name = self.name_lineEdit.text() - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - self.name in self.allNodes) - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -# ------------------------------------------------------------------------------------------------------ -# Script Editor Widget -# Wouter Gilsing built an incredibly useful python script editor for his Hotbox Manager, so I had it -# really easy for this part! -# Starting from his script editor, I changed the style and added the sublime-like functionality. -# I think this bit of code has the potential to get used in many nuke tools. -# Credit to him: http://www.woutergilsing.com/ -# Originally used on W_Hotbox v1.5: http://www.nukepedia.com/python/ui/w_hotbox -# ------------------------------------------------------------------------------------------------------ -class KnobScripterTextEdit(QtWidgets.QPlainTextEdit): - # Signal that will be emitted when the user has changed the text - userChangedEvent = QtCore.Signal() - - def __init__(self, knobScripter=""): - super(KnobScripterTextEdit, self).__init__() - - self.knobScripter = knobScripter - self.selected_text = "" - - # Setup line numbers - if self.knobScripter != "": - self.tabSpaces = self.knobScripter.tabSpaces - else: - self.tabSpaces = 4 - self.lineNumberArea = KSLineNumberArea(self) - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) - self.updateRequest.connect(self.updateLineNumberArea) - self.updateLineNumberAreaWidth() - - # Highlight line - self.cursorPositionChanged.connect(self.highlightCurrentLine) - - # -------------------------------------------------------------------------------------------------- - # This is adapted from an original version by Wouter Gilsing. - # Extract from his original comments: - # While researching the implementation of line number, I had a look at Nuke's Blinkscript node. [..] - # thefoundry.co.uk/products/nuke/developers/100/pythonreference/nukescripts.blinkscripteditor-pysrc.html - # I stripped and modified the useful bits of the line number related parts of the code [..] - # Credits to theFoundry for writing the blinkscripteditor, best example code I could wish for. - # -------------------------------------------------------------------------------------------------- - - def lineNumberAreaWidth(self): - digits = 1 - maxNum = max(1, self.blockCount()) - while (maxNum >= 10): - maxNum /= 10 - digits += 1 - - space = 7 + self.fontMetrics().width('9') * digits - return space - - def updateLineNumberAreaWidth(self): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - - def updateLineNumberArea(self, rect, dy): - - if (dy): - self.lineNumberArea.scroll(0, dy) - else: - self.lineNumberArea.update( - 0, rect.y(), self.lineNumberArea.width(), rect.height()) - - if (rect.contains(self.viewport().rect())): - self.updateLineNumberAreaWidth() - - def resizeEvent(self, event): - QtWidgets.QPlainTextEdit.resizeEvent(self, event) - - cr = self.contentsRect() - self.lineNumberArea.setGeometry(QtCore.QRect( - cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) - - def lineNumberAreaPaintEvent(self, event): - - if self.isReadOnly(): - return - - painter = QtGui.QPainter(self.lineNumberArea) - painter.fillRect(event.rect(), QtGui.QColor(36, 36, 36)) # Number bg - - block = self.firstVisibleBlock() - blockNumber = block.blockNumber() - top = int(self.blockBoundingGeometry( - block).translated(self.contentOffset()).top()) - bottom = top + int(self.blockBoundingRect(block).height()) - currentLine = self.document().findBlock( - self.textCursor().position()).blockNumber() - - painter.setPen(self.palette().color(QtGui.QPalette.Text)) - - painterFont = QtGui.QFont() - painterFont.setFamily("Courier") - painterFont.setStyleHint(QtGui.QFont.Monospace) - painterFont.setFixedPitch(True) - if self.knobScripter != "": - painterFont.setPointSize(self.knobScripter.fontSize) - painter.setFont(self.knobScripter.script_editor_font) - - while (block.isValid() and top <= event.rect().bottom()): - - textColor = QtGui.QColor(110, 110, 110) # Numbers - - if blockNumber == currentLine and self.hasFocus(): - textColor = QtGui.QColor(255, 170, 0) # Number highlighted - - painter.setPen(textColor) - - number = "%s" % str(blockNumber + 1) - painter.drawText(-3, top, self.lineNumberArea.width(), - self.fontMetrics().height(), QtCore.Qt.AlignRight, number) - - # Move to the next block - block = block.next() - top = bottom - bottom = top + int(self.blockBoundingRect(block).height()) - blockNumber += 1 - - def keyPressEvent(self, event): - ''' - Custom actions for specific keystrokes - ''' - key = event.key() - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - pre_scroll = self.verticalScrollBar().value() - #modifiers = QtWidgets.QApplication.keyboardModifiers() - #ctrl = (modifiers == Qt.ControlModifier) - #shift = (modifiers == Qt.ShiftModifier) - - up_arrow = 16777235 - down_arrow = 16777237 - - # if Tab convert to Space - if key == 16777217: - self.indentation('indent') - - # if Shift+Tab remove indent - elif key == 16777218: - self.indentation('unindent') - - # if BackSpace try to snap to previous indent level - elif key == 16777219: - if not self.unindentBackspace(): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - # COOL BEHAVIORS SIMILAR TO SUBLIME GO NEXT! - cursor = self.textCursor() - cpos = cursor.position() - apos = cursor.anchor() - text_before_cursor = self.toPlainText()[:min(cpos, apos)] - text_after_cursor = self.toPlainText()[max(cpos, apos):] - text_all = self.toPlainText() - to_line_start = text_before_cursor[::-1].find("\n") - if to_line_start == -1: - # Position of the start of the line that includes the cursor selection start - linestart_pos = 0 - else: - linestart_pos = len(text_before_cursor) - to_line_start - - to_line_end = text_after_cursor.find("\n") - if to_line_end == -1: - # Position of the end of the line that includes the cursor selection end - lineend_pos = len(text_all) - else: - lineend_pos = max(cpos, apos) + to_line_end - - text_before_lines = text_all[:linestart_pos] - text_after_lines = text_all[lineend_pos:] - if len(text_after_lines) and text_after_lines.startswith("\n"): - text_after_lines = text_after_lines[1:] - text_lines = text_all[linestart_pos:lineend_pos] - - if cursor.hasSelection(): - selection = cursor.selection().toPlainText() - else: - selection = "" - if key == Qt.Key_ParenLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # ( - cursor.insertText("(" + selection + ")") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ) - elif key == Qt.Key_ParenRight and text_after_cursor.startswith(")"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BracketLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # [ - cursor.insertText("[" + selection + "]") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ] - elif key in [Qt.Key_BracketRight, 43] and text_after_cursor.startswith("]"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BraceLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # { - cursor.insertText("{" + selection + "}") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # } - elif key in [199, Qt.Key_BraceRight] and text_after_cursor.startswith("}"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == 34: # " - if len(selection) > 0: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith('"') and '"' in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 39: # ' - if len(selection) > 0: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith("'") and "'" in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 35 and len(selection): # (yes, a hash) - # If there's a selection, insert a hash at the start of each line.. how the fuck? - if selection != "": - selection_split = selection.split("\n") - if all(i.startswith("#") for i in selection_split): - selection_commented = "\n".join( - [s[1:] for s in selection_split]) # Uncommented - else: - selection_commented = "#" + "\n#".join(selection_split) - cursor.insertText(selection_commented) - if apos > cpos: - cursor.setPosition( - apos + len(selection_commented) - len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos, QtGui.QTextCursor.KeepAnchor) - else: - cursor.setPosition(apos, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection_commented) - len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - elif key == 68 and ctrl and shift: # Ctrl+Shift+D, to duplicate text or line/s - - if not len(selection): - self.setPlainText( - text_before_lines + text_lines + "\n" + text_lines + "\n" + text_after_lines) - cursor.setPosition( - apos + len(text_lines) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(text_lines) + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - else: - if text_before_cursor.endswith("\n") and not selection.startswith("\n"): - cursor.insertText(selection + "\n" + selection) - cursor.setPosition( - apos + len(selection) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection) + 1, QtGui.QTextCursor.KeepAnchor) - else: - cursor.insertText(selection + selection) - cursor.setPosition( - apos + len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # Ctrl+Shift+Up, to move the selected line/s up - elif key == up_arrow and ctrl and shift and len(text_before_lines): - prev_line_start_distance = text_before_lines[:-1][::-1].find( - "\n") - if prev_line_start_distance == -1: - prev_line_start_pos = 0 # Position of the start of the previous line - else: - prev_line_start_pos = len( - text_before_lines) - 1 - prev_line_start_distance - prev_line = text_before_lines[prev_line_start_pos:] - - text_before_prev_line = text_before_lines[:prev_line_start_pos] - - if prev_line.endswith("\n"): - prev_line = prev_line[:-1] - - if len(text_after_lines): - text_after_lines = "\n" + text_after_lines - - self.setPlainText( - text_before_prev_line + text_lines + "\n" + prev_line + text_after_lines) - cursor.setPosition(apos - len(prev_line) - 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos - len(prev_line) - 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - elif key == down_arrow and ctrl and shift: # Ctrl+Shift+Up, to move the selected line/s up - if not len(text_after_lines): - text_after_lines = "" - next_line_end_distance = text_after_lines.find("\n") - if next_line_end_distance == -1: - next_line_end_pos = len(text_all) - else: - next_line_end_pos = next_line_end_distance - next_line = text_after_lines[:next_line_end_pos] - text_after_next_line = text_after_lines[next_line_end_pos:] - - self.setPlainText(text_before_lines + next_line + - "\n" + text_lines + text_after_next_line) - cursor.setPosition(apos + len(next_line) + 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + len(next_line) + 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - # If up key and nothing happens, go to start - elif key == up_arrow and not len(text_before_lines): - if not shift: - cursor.setPosition(0, QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition(0, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # If up key and nothing happens, go to start - elif key == down_arrow and not len(text_after_lines): - if not shift: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # if enter or return, match indent level - elif key in [16777220, 16777221]: - self.indentNewLine() - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - - self.scrollToCursor() - - def scrollToCursor(self): - self.cursor = self.textCursor() - # Does nothing, but makes the scroll go to the right place... - self.cursor.movePosition(QtGui.QTextCursor.NoMove) - self.setTextCursor(self.cursor) - - def getCursorInfo(self): - - self.cursor = self.textCursor() - - self.firstChar = self.cursor.selectionStart() - self.lastChar = self.cursor.selectionEnd() - - self.noSelection = False - if self.firstChar == self.lastChar: - self.noSelection = True - - self.originalPosition = self.cursor.position() - self.cursorBlockPos = self.cursor.positionInBlock() - - def unindentBackspace(self): - ''' - #snap to previous indent level - ''' - self.getCursorInfo() - - if not self.noSelection or self.cursorBlockPos == 0: - return False - - # check text in front of cursor - textInFront = self.document().findBlock( - self.firstChar).text()[:self.cursorBlockPos] - - # check whether solely spaces - if textInFront != ' ' * self.cursorBlockPos: - return False - - # snap to previous indent level - spaces = len(textInFront) - for space in range(spaces - ((spaces - 1) / self.tabSpaces) * self.tabSpaces - 1): - self.cursor.deletePreviousChar() - - def indentNewLine(self): - - # in case selection covers multiple line, make it one line first - self.insertPlainText('') - - self.getCursorInfo() - - # check how many spaces after cursor - text = self.document().findBlock(self.firstChar).text() - - textInFront = text[:self.cursorBlockPos] - - if len(textInFront) == 0: - self.insertPlainText('\n') - return - - indentLevel = 0 - for i in textInFront: - if i == ' ': - indentLevel += 1 - else: - break - - indentLevel /= self.tabSpaces - - # find out whether textInFront's last character was a ':' - # if that's the case add another indent. - # ignore any spaces at the end, however also - # make sure textInFront is not just an indent - if textInFront.count(' ') != len(textInFront): - while textInFront[-1] == ' ': - textInFront = textInFront[:-1] - - if textInFront[-1] == ':': - indentLevel += 1 - - # new line - self.insertPlainText('\n') - # match indent - self.insertPlainText(' ' * (self.tabSpaces * indentLevel)) - - def indentation(self, mode): - - pre_scroll = self.verticalScrollBar().value() - self.getCursorInfo() - - # if nothing is selected and mode is set to indent, simply insert as many - # space as needed to reach the next indentation level. - if self.noSelection and mode == 'indent': - - remainingSpaces = self.tabSpaces - \ - (self.cursorBlockPos % self.tabSpaces) - self.insertPlainText(' ' * remainingSpaces) - return - - selectedBlocks = self.findBlocks(self.firstChar, self.lastChar) - beforeBlocks = self.findBlocks( - last=self.firstChar - 1, exclude=selectedBlocks) - afterBlocks = self.findBlocks( - first=self.lastChar + 1, exclude=selectedBlocks) - - beforeBlocksText = self.blocks2list(beforeBlocks) - selectedBlocksText = self.blocks2list(selectedBlocks, mode) - afterBlocksText = self.blocks2list(afterBlocks) - - combinedText = '\n'.join( - beforeBlocksText + selectedBlocksText + afterBlocksText) - - # make sure the line count stays the same - originalBlockCount = len(self.toPlainText().split('\n')) - combinedText = '\n'.join(combinedText.split('\n')[:originalBlockCount]) - - self.clear() - self.setPlainText(combinedText) - - if self.noSelection: - self.cursor.setPosition(self.lastChar) - - # check whether the the original selection was from top to bottom or vice versa - else: - if self.originalPosition == self.firstChar: - first = self.lastChar - last = self.firstChar - firstBlockSnap = QtGui.QTextCursor.EndOfBlock - lastBlockSnap = QtGui.QTextCursor.StartOfBlock - else: - first = self.firstChar - last = self.lastChar - firstBlockSnap = QtGui.QTextCursor.StartOfBlock - lastBlockSnap = QtGui.QTextCursor.EndOfBlock - - self.cursor.setPosition(first) - self.cursor.movePosition( - firstBlockSnap, QtGui.QTextCursor.MoveAnchor) - self.cursor.setPosition(last, QtGui.QTextCursor.KeepAnchor) - self.cursor.movePosition( - lastBlockSnap, QtGui.QTextCursor.KeepAnchor) - - self.setTextCursor(self.cursor) - self.verticalScrollBar().setValue(pre_scroll) - - def findBlocks(self, first=0, last=None, exclude=[]): - blocks = [] - if last == None: - last = self.document().characterCount() - for pos in range(first, last + 1): - block = self.document().findBlock(pos) - if block not in blocks and block not in exclude: - blocks.append(block) - return blocks - - def blocks2list(self, blocks, mode=None): - text = [] - for block in blocks: - blockText = block.text() - if mode == 'unindent': - if blockText.startswith(' ' * self.tabSpaces): - blockText = blockText[self.tabSpaces:] - self.lastChar -= self.tabSpaces - elif blockText.startswith('\t'): - blockText = blockText[1:] - self.lastChar -= 1 - - elif mode == 'indent': - blockText = ' ' * self.tabSpaces + blockText - self.lastChar += self.tabSpaces - - text.append(blockText) - - return text - - def highlightCurrentLine(self): - ''' - Highlight currently selected line - ''' - extraSelections = [] - - selection = QtWidgets.QTextEdit.ExtraSelection() - - lineColor = QtGui.QColor(62, 62, 62, 255) - - selection.format.setBackground(lineColor) - selection.format.setProperty( - QtGui.QTextFormat.FullWidthSelection, True) - selection.cursor = self.textCursor() - selection.cursor.clearSelection() - - extraSelections.append(selection) - - self.setExtraSelections(extraSelections) - self.scrollToCursor() - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - -class KSLineNumberArea(QtWidgets.QWidget): - def __init__(self, scriptEditor): - super(KSLineNumberArea, self).__init__(scriptEditor) - - self.scriptEditor = scriptEditor - self.setStyleSheet("text-align: center;") - - def paintEvent(self, event): - self.scriptEditor.lineNumberAreaPaintEvent(event) - return - - -class KSScriptEditorHighlighter(QtGui.QSyntaxHighlighter): - ''' - This is also adapted from an original version by Wouter Gilsing. His comments: - - Modified, simplified version of some code found I found when researching: - wiki.python.org/moin/PyQt/Python%20syntax%20highlighting - They did an awesome job, so credits to them. I only needed to make some - modifications to make it fit my needs. - ''' - - def __init__(self, document, parent=None): - - super(KSScriptEditorHighlighter, self).__init__(document) - self.knobScripter = parent - self.script_editor = self.knobScripter.script_editor - self.selected_text = "" - self.selected_text_prev = "" - self.rules_sublime = "" - - self.styles = { - 'keyword': self.format([238, 117, 181], 'bold'), - 'string': self.format([242, 136, 135]), - 'comment': self.format([143, 221, 144]), - 'numbers': self.format([174, 129, 255]), - 'custom': self.format([255, 170, 0], 'italic'), - 'selected': self.format([255, 255, 255], 'bold underline'), - 'underline': self.format([240, 240, 240], 'underline'), - } - - self.keywords = [ - 'and', 'assert', 'break', 'class', 'continue', 'def', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - - self.operatorKeywords = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.variableKeywords = ['int', 'str', - 'float', 'bool', 'list', 'dict', 'set'] - - self.numbers = ['True', 'False', 'None'] - self.loadAltStyles() - - self.tri_single = (QtCore.QRegExp("'''"), 1, self.styles['comment']) - self.tri_double = (QtCore.QRegExp('"""'), 2, self.styles['comment']) - - # rules - rules = [] - - rules += [(r'\b%s\b' % i, 0, self.styles['keyword']) - for i in self.keywords] - rules += [(i, 0, self.styles['keyword']) - for i in self.operatorKeywords] - rules += [(r'\b%s\b' % i, 0, self.styles['numbers']) - for i in self.numbers] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles['comment']), - ] - - # Build a QRegExp for each pattern - self.rules_nuke = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - self.rules = self.rules_nuke - - def loadAltStyles(self): - ''' Loads other color styles apart from Nuke's default. ''' - self.styles_sublime = { - 'base': self.format([255, 255, 255]), - 'keyword': self.format([237, 36, 110]), - 'string': self.format([237, 229, 122]), - 'comment': self.format([125, 125, 125]), - 'numbers': self.format([165, 120, 255]), - 'functions': self.format([184, 237, 54]), - 'blue': self.format([130, 226, 255], 'italic'), - 'arguments': self.format([255, 170, 10], 'italic'), - 'custom': self.format([200, 200, 200], 'italic'), - 'underline': self.format([240, 240, 240], 'underline'), - 'selected': self.format([255, 255, 255], 'bold underline'), - } - - self.keywords_sublime = [ - 'and', 'assert', 'break', 'continue', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - self.operatorKeywords_sublime = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.baseKeywords_sublime = [ - ',', - ] - - self.customKeywords_sublime = [ - 'nuke', - ] - - self.blueKeywords_sublime = [ - 'def', 'class', 'int', 'str', 'float', 'bool', 'list', 'dict', 'set' - ] - - self.argKeywords_sublime = [ - 'self', - ] - - self.tri_single_sublime = (QtCore.QRegExp( - "'''"), 1, self.styles_sublime['comment']) - self.tri_double_sublime = (QtCore.QRegExp( - '"""'), 2, self.styles_sublime['comment']) - self.numbers_sublime = ['True', 'False', 'None'] - - # rules - - rules = [] - # First turn everything inside parentheses orange - rules += [(r"def [\w]+[\s]*\((.*)\)", 1, - self.styles_sublime['arguments'])] - # Now restore unwanted stuff... - rules += [(i, 0, self.styles_sublime['base']) - for i in self.baseKeywords_sublime] - rules += [(r"[^\(\w),.][\s]*[\w]+", 0, self.styles_sublime['base'])] - - # Everything else - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['keyword']) - for i in self.keywords_sublime] - rules += [(i, 0, self.styles_sublime['keyword']) - for i in self.operatorKeywords_sublime] - rules += [(i, 0, self.styles_sublime['custom']) - for i in self.customKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['blue']) - for i in self.blueKeywords_sublime] - rules += [(i, 0, self.styles_sublime['arguments']) - for i in self.argKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['numbers']) - for i in self.numbers_sublime] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles_sublime['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles_sublime['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles_sublime['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles_sublime['comment']), - # Function definitions - (r"def[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class definitions - (r"class[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class argument (which is also a class so must be green) - (r"class[\s]+[\w\.]+[\s]*\((.*)\)", - 1, self.styles_sublime['functions']), - # Function arguments also pick their style... - (r"def[\s]+[\w]+[\s]*\(([\w]+)", 1, - self.styles_sublime['arguments']), - ] - - # Build a QRegExp for each pattern - self.rules_sublime = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - def highlightBlock(self, text): - ''' - Apply syntax highlighting to the given block of text. - ''' - # Do other syntax formatting - - if self.knobScripter.color_scheme: - self.color_scheme = self.knobScripter.color_scheme - else: - self.color_scheme = "nuke" - - if self.color_scheme == "nuke": - self.rules = self.rules_nuke - elif self.color_scheme == "sublime": - self.rules = self.rules_sublime - - for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) - - while index >= 0: - # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) - - self.setCurrentBlockState(0) - - # Multi-line strings etc. based on selected scheme - if self.color_scheme == "nuke": - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - elif self.color_scheme == "sublime": - in_multiline = self.match_multiline(text, *self.tri_single_sublime) - if not in_multiline: - in_multiline = self.match_multiline( - text, *self.tri_double_sublime) - - # TODO if there's a selection, highlight same occurrences in the full document. If no selection but something highlighted, unhighlight full document. (do it thru regex or sth) - - def match_multiline(self, text, delimiter, in_state, style): - ''' - Check whether highlighting requires multiple lines. - ''' - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - start = delimiter.indexIn(text) - # Move past this match - add = delimiter.matchedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - end = delimiter.indexIn(text, start + add) - # Ending delimiter on this line? - if end >= add: - length = end - start + add + delimiter.matchedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, style) - # Look for the next match - start = delimiter.indexIn(text, start + length) - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False - -# -------------------------------------------------------------------------------------- -# Script Output Widget -# The output logger works the same way as Nuke's python script editor output window -# -------------------------------------------------------------------------------------- - - -class ScriptOutputWidget(QtWidgets.QTextEdit): - def __init__(self, parent=None): - super(ScriptOutputWidget, self).__init__(parent) - self.knobScripter = parent - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - self.setMinimumHeight(20) - - def keyPressEvent(self, event): - ctrl = ((event.modifiers() and (Qt.ControlModifier)) != 0) - alt = ((event.modifiers() and (Qt.AltModifier)) != 0) - shift = ((event.modifiers() and (Qt.ShiftModifier)) != 0) - key = event.key() - if type(event) == QtGui.QKeyEvent: - # print event.key() - if key in [32]: # Space - return KnobScripter.keyPressEvent(self.knobScripter, event) - elif key in [Qt.Key_Backspace, Qt.Key_Delete]: - self.knobScripter.clearConsole() - return QtWidgets.QTextEdit.keyPressEvent(self, event) - - # def mousePressEvent(self, QMouseEvent): - # if QMouseEvent.button() == Qt.RightButton: - # self.knobScripter.clearConsole() - # QtWidgets.QTextEdit.mousePressEvent(self, QMouseEvent) - -# --------------------------------------------------------------------- -# Modified KnobScripterTextEdit to include snippets etc. -# --------------------------------------------------------------------- - - -class KnobScripterTextEditMain(KnobScripterTextEdit): - def __init__(self, knobScripter, output=None, parent=None): - super(KnobScripterTextEditMain, self).__init__(knobScripter) - self.knobScripter = knobScripter - self.script_output = output - self.nukeCompleter = None - self.currentNukeCompletion = None - - ######## - # FROM NUKE's SCRIPT EDITOR START - ######## - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - - # Setup completer - self.nukeCompleter = QtWidgets.QCompleter(self) - self.nukeCompleter.setWidget(self) - self.nukeCompleter.setCompletionMode( - QtWidgets.QCompleter.UnfilteredPopupCompletion) - self.nukeCompleter.setCaseSensitivity(Qt.CaseSensitive) - try: - self.nukeCompleter.setModel(QtGui.QStringListModel()) - except: - self.nukeCompleter.setModel(QtCore.QStringListModel()) - - self.nukeCompleter.activated.connect(self.insertNukeCompletion) - self.nukeCompleter.highlighted.connect(self.completerHighlightChanged) - ######## - # FROM NUKE's SCRIPT EDITOR END - ######## - - def findLongestEndingMatch(self, text, dic): - ''' - If the text ends with a key in the dictionary, it returns the key and value. - If there are several matches, returns the longest one. - False if no matches. - ''' - longest = 0 # len of longest match - match_key = None - match_snippet = "" - for key, val in dic.items(): - #match = re.search(r"[\s\.({\[,;=+-]"+key+r"(?:[\s)\]\"]+|$)",text) - match = re.search(r"[\s\.({\[,;=+-]" + key + r"$", text) - if match or text == key: - if len(key) > longest: - longest = len(key) - match_key = key - match_snippet = val - if match_key is None: - return False - return match_key, match_snippet - - def placeholderToEnd(self, text, placeholder): - '''Returns distance (int) from the first occurrence of the placeholder, to the end of the string with placeholders removed''' - search = re.search(placeholder, text) - if not search: - return -1 - from_start = search.start() - total = len(re.sub(placeholder, "", text)) - to_end = total - from_start - return to_end - - def addSnippetText(self, snippet_text): - ''' Adds the selected text as a snippet (taking care of $$, $name$ etc) to the script editor ''' - cursor_placeholder_find = r"(? 1: - cursor_len = positions[1] - positions[0] - 2 - - text = re.sub(cursor_placeholder_find, "", text) - self.cursor.insertText(text) - if placeholder_to_end >= 0: - for i in range(placeholder_to_end): - self.cursor.movePosition(QtGui.QTextCursor.PreviousCharacter) - for i in range(cursor_len): - self.cursor.movePosition( - QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(self.cursor) - - def keyPressEvent(self, event): - - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - key = event.key() - - # ADAPTED FROM NUKE's SCRIPT EDITOR: - # Get completer state - self.nukeCompleterShowing = self.nukeCompleter.popup().isVisible() - - # BEFORE ANYTHING ELSE, IF SPECIAL MODIFIERS SIMPLY IGNORE THE REST - if not self.nukeCompleterShowing and (ctrl or shift or alt): - # Bypassed! - if key not in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - KnobScripterTextEdit.keyPressEvent(self, event) - return - - # If the completer is showing - if self.nukeCompleterShowing: - tc = self.textCursor() - # If we're hitting enter, do completion - if key in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - if not self.currentNukeCompletion: - self.nukeCompleter.setCurrentRow(0) - self.currentNukeCompletion = self.nukeCompleter.currentCompletion() - # print str(self.nukeCompleter.completionModel[0]) - self.insertNukeCompletion(self.currentNukeCompletion) - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you're hitting right or escape, hide the popup - elif key == Qt.Key_Right or key == Qt.Key_Escape: - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you hit tab, escape or ctrl-space, hide the completer - elif key == Qt.Key_Tab or key == Qt.Key_Escape or (ctrl and key == Qt.Key_Space): - self.currentNukeCompletion = "" - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If none of the above, update the completion model - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # Edit completion model - colNum = tc.columnNumber() - posNum = tc.position() - inputText = self.toPlainText() - inputTextSplit = inputText.splitlines() - runningLength = 0 - currentLine = None - for line in inputTextSplit: - length = len(line) - runningLength += length - if runningLength >= posNum: - currentLine = line - break - runningLength += 1 - if currentLine: - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - self.completeNukePartUnderCursor(completionPart) - return - - if type(event) == QtGui.QKeyEvent: - if key == Qt.Key_Escape: # Close the knobscripter... - self.knobScripter.close() - elif not ctrl and not alt and not shift and event.key() == Qt.Key_Tab: - self.placeholder = "$$" - # 1. Set the cursor - self.cursor = self.textCursor() - - # 2. Save text before and after - cpos = self.cursor.position() - text_before_cursor = self.toPlainText()[:cpos] - line_before_cursor = text_before_cursor.split('\n')[-1] - text_after_cursor = self.toPlainText()[cpos:] - - # 3. Check coincidences in snippets dicts - try: # Meaning snippet found - match_key, match_snippet = self.findLongestEndingMatch( - line_before_cursor, self.knobScripter.snippets) - for i in range(len(match_key)): - self.cursor.deletePreviousChar() - # This function takes care of adding the appropriate snippet and moving the cursor... - self.addSnippetText(match_snippet) - except: # Meaning snippet not found... - # ADAPTED FROM NUKE's SCRIPT EDITOR: - tc = self.textCursor() - allCode = self.toPlainText() - colNum = tc.columnNumber() - posNum = tc.position() - - # ...and if there's text in the editor - if len(allCode.split()) > 0: - # There is text in the editor - currentLine = tc.block().text() - - # If you're not at the end of the line just add a tab - if colNum < len(currentLine): - # If there isn't a ')' directly to the right of the cursor add a tab - if currentLine[colNum:colNum + 1] != ')': - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else show the completer - else: - completionPart = currentLine[:colNum].split( - " ")[-1] - if "(" in completionPart: - completionPart = completionPart.split( - "(")[-1] - - self.completeNukePartUnderCursor( - completionPart) - - return - - # If you are at the end of the line, - else: - # If there's nothing to the right of you add a tab - if currentLine[colNum - 1:] == "" or currentLine.endswith(" "): - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else update completionPart and show the completer - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - - self.completeNukePartUnderCursor(completionPart) - return - - KnobScripterTextEdit.keyPressEvent(self, event) - elif event.key() in [Qt.Key_Enter, Qt.Key_Return]: - modifiers = QtWidgets.QApplication.keyboardModifiers() - if modifiers == QtCore.Qt.ControlModifier: - self.runScript() - else: - KnobScripterTextEdit.keyPressEvent(self, event) - else: - KnobScripterTextEdit.keyPressEvent(self, event) - - def getPyObjects(self, text): - ''' Returns a list containing all the functions, classes and variables found within the selected python text (code) ''' - matches = [] - # 1: Remove text inside triple quotes (leaving the quotes) - text_clean = '""'.join(text.split('"""')[::2]) - text_clean = '""'.join(text_clean.split("'''")[::2]) - - # 2: Remove text inside of quotes (leaving the quotes) except if \" - lines = text_clean.split("\n") - text_clean = "" - for line in lines: - line_clean = '""'.join(line.split('"')[::2]) - line_clean = '""'.join(line_clean.split("'")[::2]) - line_clean = line_clean.split("#")[0] - text_clean += line_clean + "\n" - - # 3. Split into segments (lines plus ";") - segments = re.findall(r"[^\n;]+", text_clean) - - # 4. Go case by case. - for s in segments: - # Declared vars - matches += re.findall(r"([\w\.]+)(?=[,\s\w]*=[^=]+$)", s) - # Def functions and arguments - function = re.findall(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(function): - matches += function - args = re.split(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(args) > 1: - args = args[-1] - matches += re.findall( - r"(?adrianpueyo.com, 2016-2019') - kspSignature.setOpenExternalLinks(True) - kspSignature.setStyleSheet('''color:#555;font-size:9px;''') - kspSignature.setAlignment(QtCore.Qt.AlignRight) - - fontLabel = QtWidgets.QLabel("Font:") - self.fontBox = QtWidgets.QFontComboBox() - self.fontBox.setCurrentFont(QtGui.QFont(self.font)) - self.fontBox.currentFontChanged.connect(self.fontChanged) - - fontSizeLabel = QtWidgets.QLabel("Font size:") - self.fontSizeBox = QtWidgets.QSpinBox() - self.fontSizeBox.setValue(self.oldFontSize) - self.fontSizeBox.setMinimum(6) - self.fontSizeBox.setMaximum(100) - self.fontSizeBox.valueChanged.connect(self.fontSizeChanged) - - windowWLabel = QtWidgets.QLabel("Width (px):") - windowWLabel.setToolTip("Default window width in pixels") - self.windowWBox = QtWidgets.QSpinBox() - self.windowWBox.setValue(self.knobScripter.windowDefaultSize[0]) - self.windowWBox.setMinimum(200) - self.windowWBox.setMaximum(4000) - self.windowWBox.setToolTip("Default window width in pixels") - - windowHLabel = QtWidgets.QLabel("Height (px):") - windowHLabel.setToolTip("Default window height in pixels") - self.windowHBox = QtWidgets.QSpinBox() - self.windowHBox.setValue(self.knobScripter.windowDefaultSize[1]) - self.windowHBox.setMinimum(100) - self.windowHBox.setMaximum(2000) - self.windowHBox.setToolTip("Default window height in pixels") - - # TODO: "Grab current dimensions" button - - tabSpaceLabel = QtWidgets.QLabel("Tab spaces:") - tabSpaceLabel.setToolTip("Number of spaces to add with the tab key.") - self.tabSpace2 = QtWidgets.QRadioButton("2") - self.tabSpace4 = QtWidgets.QRadioButton("4") - tabSpaceButtonGroup = QtWidgets.QButtonGroup(self) - tabSpaceButtonGroup.addButton(self.tabSpace2) - tabSpaceButtonGroup.addButton(self.tabSpace4) - self.tabSpace2.setChecked(self.knobScripter.tabSpaces == 2) - self.tabSpace4.setChecked(self.knobScripter.tabSpaces == 4) - - pinDefaultLabel = QtWidgets.QLabel("Always on top:") - pinDefaultLabel.setToolTip("Default mode of the PIN toggle.") - self.pinDefaultOn = QtWidgets.QRadioButton("On") - self.pinDefaultOff = QtWidgets.QRadioButton("Off") - pinDefaultButtonGroup = QtWidgets.QButtonGroup(self) - pinDefaultButtonGroup.addButton(self.pinDefaultOn) - pinDefaultButtonGroup.addButton(self.pinDefaultOff) - self.pinDefaultOn.setChecked(self.knobScripter.pinned == True) - self.pinDefaultOff.setChecked(self.knobScripter.pinned == False) - self.pinDefaultOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.pinDefaultOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - colorSchemeLabel = QtWidgets.QLabel("Color scheme:") - colorSchemeLabel.setToolTip("Syntax highlighting text style.") - self.colorSchemeSublime = QtWidgets.QRadioButton("subl") - self.colorSchemeNuke = QtWidgets.QRadioButton("nuke") - colorSchemeButtonGroup = QtWidgets.QButtonGroup(self) - colorSchemeButtonGroup.addButton(self.colorSchemeSublime) - colorSchemeButtonGroup.addButton(self.colorSchemeNuke) - colorSchemeButtonGroup.buttonClicked.connect(self.colorSchemeChanged) - self.colorSchemeSublime.setChecked( - self.knobScripter.color_scheme == "sublime") - self.colorSchemeNuke.setChecked( - self.knobScripter.color_scheme == "nuke") - - showLabelsLabel = QtWidgets.QLabel("Show labels:") - showLabelsLabel.setToolTip( - "Display knob labels on the knob dropdown\nOtherwise, shows the internal name only.") - self.showLabelsOn = QtWidgets.QRadioButton("On") - self.showLabelsOff = QtWidgets.QRadioButton("Off") - showLabelsButtonGroup = QtWidgets.QButtonGroup(self) - showLabelsButtonGroup.addButton(self.showLabelsOn) - showLabelsButtonGroup.addButton(self.showLabelsOff) - self.showLabelsOn.setChecked(self.knobScripter.pinned == True) - self.showLabelsOff.setChecked(self.knobScripter.pinned == False) - self.showLabelsOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.showLabelsOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - self.buttonBox = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.buttonBox.accepted.connect(self.savePrefs) - self.buttonBox.rejected.connect(self.cancelPrefs) - - # Loaded custom values - self.ksPrefs = self.knobScripter.loadPrefs() - if self.ksPrefs != []: - try: - self.fontSizeBox.setValue(self.ksPrefs['font_size']) - self.windowWBox.setValue(self.ksPrefs['window_default_w']) - self.windowHBox.setValue(self.ksPrefs['window_default_h']) - self.tabSpace2.setChecked(self.ksPrefs['tab_spaces'] == 2) - self.tabSpace4.setChecked(self.ksPrefs['tab_spaces'] == 4) - self.pinDefaultOn.setChecked(self.ksPrefs['pin_default'] == 1) - self.pinDefaultOff.setChecked(self.ksPrefs['pin_default'] == 0) - self.showLabelsOn.setChecked(self.ksPrefs['show_labels'] == 1) - self.showLabelsOff.setChecked(self.ksPrefs['show_labels'] == 0) - self.colorSchemeSublime.setChecked( - self.ksPrefs['color_scheme'] == "sublime") - self.colorSchemeNuke.setChecked( - self.ksPrefs['color_scheme'] == "nuke") - except: - pass - - # Layouts - font_layout = QtWidgets.QHBoxLayout() - font_layout.addWidget(fontLabel) - font_layout.addWidget(self.fontBox) - - fontSize_layout = QtWidgets.QHBoxLayout() - fontSize_layout.addWidget(fontSizeLabel) - fontSize_layout.addWidget(self.fontSizeBox) - - windowW_layout = QtWidgets.QHBoxLayout() - windowW_layout.addWidget(windowWLabel) - windowW_layout.addWidget(self.windowWBox) - - windowH_layout = QtWidgets.QHBoxLayout() - windowH_layout.addWidget(windowHLabel) - windowH_layout.addWidget(self.windowHBox) - - tabSpacesButtons_layout = QtWidgets.QHBoxLayout() - tabSpacesButtons_layout.addWidget(self.tabSpace2) - tabSpacesButtons_layout.addWidget(self.tabSpace4) - tabSpaces_layout = QtWidgets.QHBoxLayout() - tabSpaces_layout.addWidget(tabSpaceLabel) - tabSpaces_layout.addLayout(tabSpacesButtons_layout) - - pinDefaultButtons_layout = QtWidgets.QHBoxLayout() - pinDefaultButtons_layout.addWidget(self.pinDefaultOn) - pinDefaultButtons_layout.addWidget(self.pinDefaultOff) - pinDefault_layout = QtWidgets.QHBoxLayout() - pinDefault_layout.addWidget(pinDefaultLabel) - pinDefault_layout.addLayout(pinDefaultButtons_layout) - - showLabelsButtons_layout = QtWidgets.QHBoxLayout() - showLabelsButtons_layout.addWidget(self.showLabelsOn) - showLabelsButtons_layout.addWidget(self.showLabelsOff) - showLabels_layout = QtWidgets.QHBoxLayout() - showLabels_layout.addWidget(showLabelsLabel) - showLabels_layout.addLayout(showLabelsButtons_layout) - - colorSchemeButtons_layout = QtWidgets.QHBoxLayout() - colorSchemeButtons_layout.addWidget(self.colorSchemeSublime) - colorSchemeButtons_layout.addWidget(self.colorSchemeNuke) - colorScheme_layout = QtWidgets.QHBoxLayout() - colorScheme_layout.addWidget(colorSchemeLabel) - colorScheme_layout.addLayout(colorSchemeButtons_layout) - - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.addWidget(kspTitle) - self.master_layout.addWidget(kspSignature) - self.master_layout.addWidget(kspLine) - self.master_layout.addLayout(font_layout) - self.master_layout.addLayout(fontSize_layout) - self.master_layout.addLayout(windowW_layout) - self.master_layout.addLayout(windowH_layout) - self.master_layout.addLayout(tabSpaces_layout) - self.master_layout.addLayout(pinDefault_layout) - self.master_layout.addLayout(showLabels_layout) - self.master_layout.addLayout(colorScheme_layout) - self.master_layout.addWidget(self.buttonBox) - self.setLayout(self.master_layout) - self.setFixedSize(self.minimumSize()) - - def savePrefs(self): - self.font = self.fontBox.currentFont().family() - ks_prefs = { - 'font_size': self.fontSizeBox.value(), - 'window_default_w': self.windowWBox.value(), - 'window_default_h': self.windowHBox.value(), - 'tab_spaces': self.tabSpaceValue(), - 'pin_default': self.pinDefaultValue(), - 'show_labels': self.showLabelsValue(), - 'font': self.font, - 'color_scheme': self.colorSchemeValue(), - } - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.font = self.font - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.tabSpaces = self.tabSpaceValue() - self.knobScripter.script_editor.tabSpaces = self.tabSpaceValue() - with open(self.prefs_txt, "w") as f: - prefs = json.dump(ks_prefs, f, sort_keys=True, indent=4) - self.accept() - self.knobScripter.highlighter.rehighlight() - self.knobScripter.show_labels = self.showLabelsValue() - if self.knobScripter.nodeMode: - self.knobScripter.refreshClicked() - return prefs - - def cancelPrefs(self): - self.knobScripter.script_editor_font.setPointSize(self.oldFontSize) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.color_scheme = self.oldScheme - self.knobScripter.highlighter.rehighlight() - self.reject() - - def fontSizeChanged(self): - self.knobScripter.script_editor_font.setPointSize( - self.fontSizeBox.value()) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def fontChanged(self): - self.font = self.fontBox.currentFont().family() - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def colorSchemeChanged(self): - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.highlighter.rehighlight() - return - - def tabSpaceValue(self): - return 2 if self.tabSpace2.isChecked() else 4 - - def pinDefaultValue(self): - return 1 if self.pinDefaultOn.isChecked() else 0 - - def showLabelsValue(self): - return 1 if self.showLabelsOn.isChecked() else 0 - - def colorSchemeValue(self): - return "nuke" if self.colorSchemeNuke.isChecked() else "sublime" - - def closeEvent(self, event): - self.cancelPrefs() - self.close() - - -def updateContext(): - ''' - Get the current selection of nodes with their appropriate context - Doing this outside the KnobScripter -> forces context update inside groups when needed - ''' - global knobScripterSelectedNodes - knobScripterSelectedNodes = nuke.selectedNodes() - return - -# -------------------------------- -# FindReplace -# -------------------------------- - - -class FindReplaceWidget(QtWidgets.QWidget): - ''' SearchReplace Widget for the knobscripter. FindReplaceWidget(editor = QPlainTextEdit) ''' - - def __init__(self, parent): - super(FindReplaceWidget, self).__init__(parent) - - self.editor = parent.script_editor - - self.initUI() - - def initUI(self): - - # -------------- - # Find Row - # -------------- - - # Widgets - self.find_label = QtWidgets.QLabel("Find:") - # self.find_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.find_label.setFixedWidth(50) - self.find_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.find_lineEdit = QtWidgets.QLineEdit() - self.find_next_button = QtWidgets.QPushButton("Next") - self.find_next_button.clicked.connect(self.find) - self.find_prev_button = QtWidgets.QPushButton("Previous") - self.find_prev_button.clicked.connect(self.findBack) - self.find_lineEdit.returnPressed.connect(self.find_next_button.click) - - # Layout - self.find_layout = QtWidgets.QHBoxLayout() - self.find_layout.addWidget(self.find_label) - self.find_layout.addWidget(self.find_lineEdit, stretch=1) - self.find_layout.addWidget(self.find_next_button) - self.find_layout.addWidget(self.find_prev_button) - - # -------------- - # Replace Row - # -------------- - - # Widgets - self.replace_label = QtWidgets.QLabel("Replace:") - # self.replace_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.replace_label.setFixedWidth(50) - self.replace_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.replace_lineEdit = QtWidgets.QLineEdit() - self.replace_button = QtWidgets.QPushButton("Replace") - self.replace_button.clicked.connect(self.replace) - self.replace_all_button = QtWidgets.QPushButton("Replace All") - self.replace_all_button.clicked.connect( - lambda: self.replace(rep_all=True)) - self.replace_lineEdit.returnPressed.connect(self.replace_button.click) - - # Layout - self.replace_layout = QtWidgets.QHBoxLayout() - self.replace_layout.addWidget(self.replace_label) - self.replace_layout.addWidget(self.replace_lineEdit, stretch=1) - self.replace_layout.addWidget(self.replace_button) - self.replace_layout.addWidget(self.replace_all_button) - - # Info text - self.info_text = QtWidgets.QLabel("") - self.info_text.setVisible(False) - self.info_text.mousePressEvent = lambda x: self.info_text.setVisible( - False) - #f = self.info_text.font() - # f.setItalic(True) - # self.info_text.setFont(f) - # self.info_text.clicked.connect(lambda:self.info_text.setVisible(False)) - - # Divider line - line = QtWidgets.QFrame() - line.setFrameShape(QtWidgets.QFrame.HLine) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - line.setLineWidth(0) - line.setMidLineWidth(1) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - - # -------------- - # Main Layout - # -------------- - - self.layout = QtWidgets.QVBoxLayout() - self.layout.addSpacing(4) - self.layout.addWidget(self.info_text) - self.layout.addLayout(self.find_layout) - self.layout.addLayout(self.replace_layout) - self.layout.setSpacing(4) - try: # >n11 - self.layout.setMargin(2) - except: # 0: # If not found but there are matches, start over - cursor.movePosition(QtGui.QTextCursor.Start) - self.editor.setTextCursor(cursor) - self.editor.find(find_str, flags) - else: - cursor.insertText(rep_str) - self.editor.find( - rep_str, flags | QtGui.QTextDocument.FindBackward) - - cursor.endEditBlock() - self.replace_lineEdit.setFocus() - return - - -# -------------------------------- -# Snippets -# -------------------------------- -class SnippetsPanel(QtWidgets.QDialog): - def __init__(self, parent): - super(SnippetsPanel, self).__init__(parent) - - self.knobScripter = parent - - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.setWindowTitle("Snippet editor") - - self.snippets_txt_path = self.knobScripter.snippets_txt_path - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - #self.snippets_dict = snippets_dic - - # self.saveSnippets(snippets_dic) - - self.initUI() - self.resize(500, 300) - - def initUI(self): - self.layout = QtWidgets.QVBoxLayout() - - # First Area (Titles) - title_layout = QtWidgets.QHBoxLayout() - shortcuts_label = QtWidgets.QLabel("Shortcut") - code_label = QtWidgets.QLabel("Code snippet") - title_layout.addWidget(shortcuts_label, stretch=1) - title_layout.addWidget(code_label, stretch=2) - self.layout.addLayout(title_layout) - - # Main Scroll area - self.scroll_content = QtWidgets.QWidget() - self.scroll_layout = QtWidgets.QVBoxLayout() - - self.buildSnippetWidgets() - - self.scroll_content.setLayout(self.scroll_layout) - - # Scroll Area Properties - self.scroll = QtWidgets.QScrollArea() - self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scroll.setWidgetResizable(True) - self.scroll.setWidget(self.scroll_content) - - self.layout.addWidget(self.scroll) - - # File knob test - #self.filePath_lineEdit = SnippetFilePath(self) - # self.filePath_lineEdit - # self.layout.addWidget(self.filePath_lineEdit) - - # Lower buttons - self.bottom_layout = QtWidgets.QHBoxLayout() - - self.add_btn = QtWidgets.QPushButton("Add snippet") - self.add_btn.setToolTip("Create empty fields for an extra snippet.") - self.add_btn.clicked.connect(self.addSnippet) - self.bottom_layout.addWidget(self.add_btn) - - self.addPath_btn = QtWidgets.QPushButton("Add custom path") - self.addPath_btn.setToolTip( - "Add a custom path to an external snippets .txt file.") - self.addPath_btn.clicked.connect(self.addCustomPath) - self.bottom_layout.addWidget(self.addPath_btn) - - self.bottom_layout.addStretch() - - self.save_btn = QtWidgets.QPushButton('OK') - self.save_btn.setToolTip( - "Save the snippets into a json file and close the panel.") - self.save_btn.clicked.connect(self.okPressed) - self.bottom_layout.addWidget(self.save_btn) - - self.cancel_btn = QtWidgets.QPushButton("Cancel") - self.cancel_btn.setToolTip("Cancel any new snippets or modifications.") - self.cancel_btn.clicked.connect(self.close) - self.bottom_layout.addWidget(self.cancel_btn) - - self.apply_btn = QtWidgets.QPushButton('Apply') - self.apply_btn.setToolTip("Save the snippets into a json file.") - self.apply_btn.setShortcut('Ctrl+S') - self.apply_btn.clicked.connect(self.applySnippets) - self.bottom_layout.addWidget(self.apply_btn) - - self.help_btn = QtWidgets.QPushButton('Help') - self.help_btn.setShortcut('F1') - self.help_btn.clicked.connect(self.showHelp) - self.bottom_layout.addWidget(self.help_btn) - - self.layout.addLayout(self.bottom_layout) - - self.setLayout(self.layout) - - def reload(self): - ''' - Clears everything without saving and redoes the widgets etc. - Only to be called if the panel isn't shown meaning it's closed. - ''' - for i in reversed(range(self.scroll_layout.count())): - self.scroll_layout.itemAt(i).widget().deleteLater() - - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - - self.buildSnippetWidgets() - - def buildSnippetWidgets(self): - for i, (key, val) in enumerate(self.snippets_dict.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - file_edit = SnippetFilePath(val) - self.scroll_layout.insertWidget(-1, file_edit) - else: - snippet_edit = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(-1, snippet_edit) - - def loadSnippetsDict(self, path=""): - ''' Load prefs. TO REMOVE ''' - if path == "": - path = self.knobScripter.snippets_txt_path - if not os.path.isfile(self.snippets_txt_path): - return {} - else: - with open(self.snippets_txt_path, "r") as f: - self.snippets = json.load(f) - return self.snippets - - def getSnippetsAsDict(self): - dic = {} - num_snippets = self.scroll_layout.count() - path_i = 1 - for s in range(num_snippets): - se = self.scroll_layout.itemAt(s).widget() - if se.__class__.__name__ == "SnippetEdit": - key = se.shortcut_editor.text() - val = se.script_editor.toPlainText() - if key != "": - dic[key] = val - else: - path = se.filepath_lineEdit.text() - if path != "": - dic["[custom-path-{}]".format(str(path_i))] = path - path_i += 1 - return dic - - def saveSnippets(self, snippets=""): - if snippets == "": - snippets = self.getSnippetsAsDict() - with open(self.snippets_txt_path, "w") as f: - prefs = json.dump(snippets, f, sort_keys=True, indent=4) - return prefs - - def applySnippets(self): - self.saveSnippets() - self.knobScripter.snippets = self.knobScripter.loadSnippets(maxDepth=5) - self.knobScripter.loadSnippets() - - def okPressed(self): - self.applySnippets() - self.accept() - - def addSnippet(self, key="", val=""): - se = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(0, se) - self.show() - return se - - def addCustomPath(self, path=""): - cpe = SnippetFilePath(path) - self.scroll_layout.insertWidget(0, cpe) - self.show() - cpe.browseSnippets() - return cpe - - def showHelp(self): - ''' Create a new snippet, auto-completed with the help ''' - help_key = "help" - help_val = """Snippets are a convenient way to have code blocks that you can call through a shortcut.\n\n1. Simply write a shortcut on the text input field on the left. You can see this one is set to "test".\n\n2. Then, write a code or whatever in this script editor. You can include $$ as the placeholder for where you'll want the mouse cursor to appear.\n\n3. Finally, click OK or Apply to save the snippets. On the main script editor, you'll be able to call any snippet by writing the shortcut (in this example: help) and pressing the Tab key.\n\nIn order to remove a snippet, simply leave the shortcut and contents blank, and save the snippets.""" - help_se = self.addSnippet(help_key, help_val) - help_se.script_editor.resize(160, 160) - - -class SnippetEdit(QtWidgets.QWidget): - ''' Simple widget containing two fields, for the snippet shortcut and content ''' - - def __init__(self, key="", val="", parent=None): - super(SnippetEdit, self).__init__(parent) - - self.knobScripter = parent.knobScripter - self.color_scheme = self.knobScripter.color_scheme - self.layout = QtWidgets.QHBoxLayout() - - self.shortcut_editor = QtWidgets.QLineEdit(self) - f = self.shortcut_editor.font() - f.setWeight(QtGui.QFont.Bold) - self.shortcut_editor.setFont(f) - self.shortcut_editor.setText(str(key)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.script_editor = KnobScripterTextEdit() - self.script_editor.setMinimumHeight(100) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor_font = self.knobScripter.script_editor_font - self.script_editor.setFont(self.script_editor_font) - self.script_editor.resize(90, 90) - self.script_editor.setPlainText(str(val)) - self.layout.addWidget(self.shortcut_editor, - stretch=1, alignment=Qt.AlignTop) - self.layout.addWidget(self.script_editor, stretch=2) - self.layout.setContentsMargins(0, 0, 0, 0) - - self.setLayout(self.layout) - - -class SnippetFilePath(QtWidgets.QWidget): - ''' Simple widget containing a filepath lineEdit and a button to open the file browser ''' - - def __init__(self, path="", parent=None): - super(SnippetFilePath, self).__init__(parent) - - self.layout = QtWidgets.QHBoxLayout() - - self.custompath_label = QtWidgets.QLabel(self) - self.custompath_label.setText("Custom path: ") - - self.filepath_lineEdit = QtWidgets.QLineEdit(self) - self.filepath_lineEdit.setText(str(path)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.filepath_lineEdit.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily("Courier") - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(11) - self.filepath_lineEdit.setFont(self.script_editor_font) - - self.file_button = QtWidgets.QPushButton(self) - self.file_button.setText("Browse...") - self.file_button.clicked.connect(self.browseSnippets) - - self.layout.addWidget(self.custompath_label) - self.layout.addWidget(self.filepath_lineEdit) - self.layout.addWidget(self.file_button) - self.layout.setContentsMargins(0, 10, 0, 10) - - self.setLayout(self.layout) - - def browseSnippets(self): - ''' Opens file panel for ...snippets.txt ''' - browseLocation = nuke.getFilename('Select snippets file', '*.txt') - - if not browseLocation: - return - - self.filepath_lineEdit.setText(browseLocation) - return - - -# -------------------------------- -# Implementation -# -------------------------------- - -def showKnobScripter(knob="knobChanged"): - selection = nuke.selectedNodes() - if not len(selection): - pan = KnobScripter() - else: - pan = KnobScripter(selection[0], knob) - pan.show() - - -def addKnobScripterPanel(): - global knobScripterPanel - try: - knobScripterPanel = panels.registerWidgetAsPanel('nuke.KnobScripterPane', 'Knob Scripter', - 'com.adrianpueyo.KnobScripterPane') - knobScripterPanel.addToPane(nuke.getPaneFor('Properties.1')) - - except: - knobScripterPanel = panels.registerWidgetAsPanel( - 'nuke.KnobScripterPane', 'Knob Scripter', 'com.adrianpueyo.KnobScripterPane') - - -nuke.KnobScripterPane = KnobScripterPane -log("KS LOADED") -ksShortcut = "alt+z" -addKnobScripterPanel() -nuke.menu('Nuke').addCommand( - 'Edit/Node/Open Floating Knob Scripter', showKnobScripter, ksShortcut) -nuke.menu('Nuke').addCommand('Edit/Node/Update KnobScripter Context', - updateContext).setVisible(False) diff --git a/openpype/hosts/nuke/startup/init.py b/openpype/hosts/nuke/startup/init.py deleted file mode 100644 index d7560814bf..0000000000 --- a/openpype/hosts/nuke/startup/init.py +++ /dev/null @@ -1,4 +0,0 @@ -import nuke - -# default write mov -nuke.knobDefault('Write.mov.colorspace', 'sRGB') From c25cb9e1f5749b4dd071c873b94fd1e5be1193bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:03:02 +0200 Subject: [PATCH 178/258] fixed missing "parent" in fields --- openpype/client/entities.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a56288c1e8..1bfab5ad57 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields): +def _prepare_fields(fields, ensure_fields=None): if not fields: return None @@ -33,6 +33,10 @@ def _prepare_fields(fields): } if "_id" not in output: output["_id"] = True + + if ensure_fields: + for key in ensure_fields: + output[key] = True return output @@ -655,9 +659,8 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] - fields = _prepare_fields(fields) - if fields and "parent" not in fields: - fields.append("parent") + + fields = _prepare_fields(fields, ["parent"]) version_docs = get_versions( project_name, version_ids=version_ids, fields=fields From 67532139b989f6e831400a12c21dab33516d3004 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:05:00 +0200 Subject: [PATCH 179/258] changed variable name to required_fields --- openpype/client/entities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1bfab5ad57..cc4032712c 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields, ensure_fields=None): +def _prepare_fields(fields, required_fields=None): if not fields: return None @@ -34,8 +34,8 @@ def _prepare_fields(fields, ensure_fields=None): if "_id" not in output: output["_id"] = True - if ensure_fields: - for key in ensure_fields: + if required_fields: + for key in required_fields: output[key] = True return output From ca926cf3102d0ddb40d17e88c117415773314278 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:05:23 +0200 Subject: [PATCH 180/258] nuke: adding extract thumbnail settings --- .../defaults/project_settings/nuke.json | 3 ++ .../schemas/schema_nuke_publish.json | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 16348bec85..6c45e2a9c1 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -166,6 +166,9 @@ }, "ExtractThumbnail": { "enabled": true, + "use_rendered": true, + "bake_viewer_process": true, + "bake_viewer_input_process": true, "nodes": { "Reformat": [ [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 04df957d67..575bfe79e7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -135,9 +135,31 @@ "label": "Enabled" }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" + "type": "boolean", + "key": "use_rendered", + "label": "Use rendered images" + }, + { + "type": "boolean", + "key": "bake_viewer_process", + "label": "Bake viewer process" + }, + { + "type": "boolean", + "key": "bake_viewer_input_process", + "label": "Bake viewer input process" + }, + { + "type": "collapsible-wrap", + "label": "Nodes", + "collapsible": true, + "children": [ + { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + } + ] } ] }, From e21106aa9250d5317a157adcac7b38cffab7d990 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:06:19 +0200 Subject: [PATCH 181/258] nuke: adding new attributes to extract thumnail --- .../nuke/plugins/publish/extract_thumbnail.py | 96 +++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ef6d486ca2..ce01f12a41 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -23,9 +23,13 @@ class ExtractThumbnail(openpype.api.Extractor): families = ["review"] hosts = ["nuke"] - # presets + # settings + use_rendered = False + bake_viewer_process = True + bake_viewer_input_process = True nodes = {} + def process(self, instance): if "render.farm" in instance.data["families"]: return @@ -53,48 +57,58 @@ class ExtractThumbnail(openpype.api.Extractor): "StagingDir `{0}`...".format(instance.data["stagingDir"])) temporary_nodes = [] - collection = instance.data.get("collection", None) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + # try to connect already rendered images + if self.use_rendered: + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) - else: - fname = os.path.basename(instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + # get first and last frame + first_frame = min(collection.indexes) + last_frame = max(collection.indexes) + else: + fname = os.path.basename(instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." + first_frame = instance.data.get("frameStart", None) + last_frame = instance.data.get("frameEnd", None) - path_render = os.path.join(staging_dir, fname).replace("\\", "/") - # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + self.log.debug("__ fhead: `{}`".format(fhead)) - rnode["file"].setValue(path_render) + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + path_render = os.path.join(staging_dir, fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) + # check if file exist otherwise connect to write node + if os.path.isfile(path_render): + rnode = nuke.createNode("Read") + + rnode["file"].setValue(path_render) + + rnode["first"].setValue(first_frame) + rnode["origfirst"].setValue(first_frame) + rnode["last"].setValue(last_frame) + rnode["origlast"].setValue(last_frame) + temporary_nodes.append(rnode) + previous_node = rnode + else: + previous_node = node + + # bake viewer input look node into thumbnail image + if self.bake_viewer_input_process: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) reformat_node = nuke.createNode("Reformat") @@ -110,10 +124,12 @@ class ExtractThumbnail(openpype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # bake viewer colorspace into thumbnail image + if self.bake_viewer_process: + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") From 79f81b6b36bb94d077f1b3ef7e35db06e68b002f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 21:09:47 +0200 Subject: [PATCH 182/258] nuke: refactory extract thumbnail for new settings attributes --- .../nuke/plugins/publish/extract_thumbnail.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ce01f12a41..092fc07d6c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -42,11 +42,17 @@ class ExtractThumbnail(openpype.api.Extractor): self.render_thumbnail(instance) def render_thumbnail(self, instance): + first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] + + # find frame range and define middle thumb frame + mid_frame = int((last_frame - first_frame) / 2) + node = instance[0] # group node self.log.info("Creating staging dir...") if "representations" not in instance.data: - instance.data["representations"] = list() + instance.data["representations"] = [] staging_dir = os.path.normpath( os.path.dirname(instance.data['path'])) @@ -69,21 +75,19 @@ class ExtractThumbnail(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) + thumb_fname = list(collection)[mid_frame] else: - fname = os.path.basename(instance.data.get("path", None)) + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) self.log.debug("__ fhead: `{}`".format(fhead)) if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join(staging_dir, fname).replace("\\", "/") + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") self.log.debug("__ path_render: `{}`".format(path_render)) # check if file exist otherwise connect to write node @@ -92,10 +96,13 @@ class ExtractThumbnail(openpype.api.Extractor): rnode["file"].setValue(path_render) - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) + temporary_nodes.append(rnode) previous_node = rnode else: @@ -144,26 +151,18 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes.append(write_node) tags = ["thumbnail", "publish_on_farm"] - # retime for - mid_frame = int((int(last_frame) - int(first_frame)) / 2) \ - + int(first_frame) - first_frame = int(last_frame) / 2 - last_frame = int(last_frame) / 2 - repre = { 'name': name, 'ext': "jpg", "outputName": "thumb", 'files': file, "stagingDir": staging_dir, - "frameStart": first_frame, - "frameEnd": last_frame, "tags": tags } instance.data["representations"].append(repre) # Render frames - nuke.execute(write_node.name(), int(mid_frame), int(mid_frame)) + nuke.execute(write_node.name(), mid_frame, mid_frame) self.log.debug( "representations: {}".format(instance.data["representations"])) From 75b6acb0a4af5491e5a38a287f8ead9f88381123 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Jun 2022 18:47:58 +0200 Subject: [PATCH 183/258] OP-3214 - removed wrong targets on Extractor This caused missing extractors that should be running on a farm (Slate extractor for Nuke...). Explicitly set all plugins creating jobs on DL to be triggered only on local. --- openpype/api.py | 2 -- .../plugins/publish/submit_aftereffects_deadline.py | 1 + .../plugins/publish/submit_harmony_deadline.py | 1 + .../deadline/plugins/publish/submit_maya_deadline.py | 1 + .../publish/submit_maya_remote_publish_deadline.py | 3 ++- .../deadline/plugins/publish/submit_nuke_deadline.py | 1 + .../deadline/plugins/publish/submit_publish_job.py | 1 + openpype/plugin.py | 12 ------------ 8 files changed, 7 insertions(+), 15 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index e049a683c6..9ce745b653 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -44,7 +44,6 @@ from . import resources from .plugin import ( Extractor, - Integrator, ValidatePipelineOrder, ValidateContentsOrder, @@ -87,7 +86,6 @@ __all__ = [ # plugin classes "Extractor", - "Integrator", # ordering "ValidatePipelineOrder", "ValidateContentsOrder", diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index b6584f239e..de8df3dd9e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -33,6 +33,7 @@ class AfterEffectsSubmitDeadline( hosts = ["aftereffects"] families = ["render.farm"] # cannot be "render' as that is integrated use_published = True + targets = ["local"] priority = 50 chunk_size = 1000000 diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 912f0f4026..2cf502224f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -238,6 +238,7 @@ class HarmonySubmitDeadline( order = pyblish.api.IntegratorOrder + 0.1 hosts = ["harmony"] families = ["render.farm"] + targets = ["local"] optional = True use_published = False diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8562c85f7d..9964e3c646 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -287,6 +287,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.1 hosts = ["maya"] families = ["renderlayer"] + targets = ["local"] use_published = True tile_assembler_plugin = "OpenPypeTileAssembler" diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 4f82818d6d..57572fcb24 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -10,7 +10,7 @@ import openpype.api import pyblish.api -class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): +class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): """Submit Maya scene to perform a local publish in Deadline. Publishing in Deadline can be helpful for scenes that publish very slow. @@ -31,6 +31,7 @@ class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): order = pyblish.api.IntegratorOrder hosts = ["maya"] families = ["publish.farm"] + targets = ["local"] def process(self, instance): settings = get_project_settings(os.getenv("AVALON_PROJECT")) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 94c703d66d..ca68c87f9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -23,6 +23,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): hosts = ["nuke", "nukestudio"] families = ["render.farm", "prerender.farm"] optional = True + targets = ["local"] # presets priority = 50 diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..860d9fd01b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -103,6 +103,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" deadline_plugin = "OpenPype" + targets = ["local"] hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] diff --git a/openpype/plugin.py b/openpype/plugin.py index 6637ad1d8b..bb9bc2ff85 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -18,16 +18,6 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, cls).process(cls, *args, **kwargs) -class Integrator(InstancePlugin): - """Integrator base class. - - Wraps pyblish instance plugin. Targets set to "local" which means all - integrators should run on "local" publishes, by default. - "remote" targets could be used for integrators that should run externally. - """ - targets = ["local"] - - class Extractor(InstancePlugin): """Extractor base class. @@ -38,8 +28,6 @@ class Extractor(InstancePlugin): """ - targets = ["local"] - order = 2.0 def staging_dir(self, instance): From db1316dd688dc2bba62395aa968ab8af7b5ce552 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Wed, 15 Jun 2022 20:56:33 +0200 Subject: [PATCH 184/258] Did this the easy way and let's go! --- openpype/modules/ftrack/ftrack_module.py | 3 -- openpype/modules/ftrack/tray/ftrack_tray.py | 42 +------------------ .../defaults/system_settings/modules.json | 1 - .../module_settings/schema_ftrack.json | 5 --- 4 files changed, 1 insertion(+), 50 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 048e5ebfb1..f99e189082 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,9 +42,6 @@ class FtrackModule( self.ftrack_url = ftrack_url - ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] - self.ftrack_open_as_app = ftrack_open_as_app - current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e822fd4639..2919ae22fb 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,10 +2,6 @@ import os import time import datetime import threading -import platform -import posixpath -import ntpath -import webbrowser from Qt import QtCore, QtWidgets, QtGui @@ -54,43 +50,7 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - env_pf64 = os.environ['ProgramW6432'].replace( - ntpath.sep, posixpath.sep) - env_pf32 = os.environ['ProgramFiles(x86)'].replace( - ntpath.sep, posixpath.sep) - env_loc = os.environ['LocalAppData'].replace( - ntpath.sep, posixpath.sep) - chromium_paths_win = [ - f"{env_pf64}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Google/Chrome/Application/chrome.exe", - f"{env_loc}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" - ] - cur_os = cur_os = platform.system().lower() - if cur_os == "windows": - is_chromium = False - for p in chromium_paths_win: - if os.path.exists(p): - is_chromium = True - chromium_path = p - break - if is_chromium and self.module.ftrack_open_as_app: - webbrowser.get(f"{chromium_path} %s").open_new( - f"--app={self.module.ftrack_url}") - else: - webbrowser.get(using="windows-default").open_new( - self.module.ftrack_url) - - else: - if self.module.ftrack_open_as_app: - try: - webbrowser.get(using='chrome').open_new( - f"--app={self.module.ftrack_url}") - except webbrowser.Error: - webbrowser.open_new(self.module.ftrack_url) - else: - webbrowser.open_new(self.module.ftrack_url) - return + QtGui.QDesktopServices.openUrl(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 6d09652bb9..537e287366 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,7 +15,6 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 570d856cf8..654ddf2938 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -16,11 +16,6 @@ "key": "ftrack_server", "label": "Server" }, - { - "type": "boolean", - "key": "ftrack_open_as_app", - "label": "Open in app mode" - }, { "type": "splitter" }, From f87f461578b4eb2308aaff68d5576ff1743156cf Mon Sep 17 00:00:00 2001 From: pberto Date: Thu, 16 Jun 2022 15:07:01 +0900 Subject: [PATCH 185/258] docs: updated / simplified docs in light of upcoming changes --- website/docs/artist_hosts_maya_multiverse.md | 21 +++++++++++++++++- website/docs/assets/maya-multiverse_setup.png | Bin 164855 -> 458197 bytes 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_multiverse.md b/website/docs/artist_hosts_maya_multiverse.md index e6520bafa0..a173e79125 100644 --- a/website/docs/artist_hosts_maya_multiverse.md +++ b/website/docs/artist_hosts_maya_multiverse.md @@ -65,6 +65,25 @@ the one depicted here: ![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) + +``` +{ + "MULTIVERSE_PATH": "/Path/to/Multiverse-{MULTIVERSE_VERSION}", + "MAYA_MODULE_PATH": "{MULTIVERSE}/Maya;{MAYA_MODULE_PATH}" +} + +{ + "MULTIVERSE_VERSION": "7.1.0-py27" +} + +``` + +The Multiverse Maya module file (.mod) pointed above contains all the necessary +environment variables to run Multiverse. + +The OpenPype settings will contain blocks to enable/disable the Multiverse +Creators and Loader, along with sensible studio setting. + For more information about setup of Multiverse please refer to the relative page on the [Multiverse official documentation](https://multi-verse.io/docs). @@ -94,7 +113,7 @@ You can choose the USD file format in the Creators' set nodes: - Assets: `.usd` (default) or `.usda` or `.usdz` - Compositions: `.usda` (default) or `.usd` - Overrides: `.usda` (default) or `.usd` -- Looks: `.ma` +- Looks: `.ma` (default) ![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_asset_creator.png) diff --git a/website/docs/assets/maya-multiverse_setup.png b/website/docs/assets/maya-multiverse_setup.png index 8aa89ef7e504272e7b5f9abbfea0ec0ca3d1d783..72bdb0d3798f5e7234812ae3d8d8e70e2dac1bdf 100644 GIT binary patch literal 458197 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

HTBs}V9(6pl^D4!Z% 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�I+5`6?ezxjcdNB~pt+w%?zc5fPz%bdYAu~y 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�i2|0OrHG^$E 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%0DG^)#N*7t@wNCzvF#Dix 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%0Am_x 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

4B`UV(w{DUt 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 literal 164855 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

?;P|97R-)#?HUS+R~+heNwi? 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

=26e?*z3RuFR6Ga0 z#yjo=$9MDM9#^L_Y2KzlRqISfj;hXFhTIGUTc* zyKK9V_Xx!8y6Ip7Iu^u~9WJIYF?H@1i^-(ILN~W z*3A!;*_t%08}E1I2DM}=i+H0~df9GlLPHt7cFEUl*z)xnu4y`J%4jf9Qs&`7!YzpV zD`%CWcOJHE=X85s|4NLA8$>K@VI4sjmZA+4`%%!36W0dl@`^ILmkR{GOw1TnIa|+v zl<;LceOg0Z9kvBe@z#$W`Nk{A=kOA(?69-Io1dU&4wZ_Q_n`y)>`gV@-o!jo46@;- zN~`3kdCO~2v^Yh$t*t-QJ9(x$ z$AuglFgfxh0xU!msYLNo>s1)`Z&t%=Rb4gm<7s3{WDPrP4oGW_R}2w$n%Cakm2a%3 zt{#NWE^gHp&*Y7ndY7PJ4eaRa)6E5trgvrDE3PZL8Y0-ck49V7TyOp$JQKS_xTeOqMPu~Q_k&(yEIzaeODkF9!efbw zvGvjs$Vr(4VZSZ zH=XFRV{)Fk((65BjMv`sfFV`h*%|D#=Mpi4_vh+L_XfL*S+W4%8|lVKhmZ$+E-`I9 zxoe)8<7>7CZS8DdYp{B(Oe<+gNi)>+n7s{r>m459oG~tVR7RL^EX<+C4<}Dr?>-t@ z=^(PFsV=`(<8ypxZZ`7qkDJnwu=t07#owdE#FEz~sY4$sv$(j(;x~8M)%Y9hN(<%g zJulr#debdVqmKo`Skt;#y&WRt(w(pNgj;4w&_yu39=G;^n^Yf7J8m83m z>74Sc<0B&nEo!Qop2r_%;xJ8oJwV;bti#yt4dIy8y#t7|zIk(NpL=w-s)9m8`$VqA zuOFdS3w26l4(?K-8w#?rpKed}CZv@oC#NT8q$EbgaXRlm`5^x0%)n_xi3FYFur@eA zjGG|3=zl8~et{Ofo1U-p?Nj@nz&GozA37>_;KQ9;r6ovPc?TI3I@vk5M#Nq#zV@U@ z@cM{4&;pS_AlD~8c0)ixP6m5oqNy#9dkS3SCCtZc?d_i!mSn?UmBw(f!;tZdNrBg{ zVp`EJmNdg7y>HE!r5Ed8Mf-Y2eVko4uR^p|L*s!Mt47vg#0PhG*MxY;d51Hd*|zC^ zHMB^Zy!Y6}6Jd%fz+S}Zg~Vse&)pp;owuIfNzHC#(X6zw8nf}Ubn1^kbjqF^YiMd{ ztl`Vhpq`L=tH2t&HXWj6t(?^?^2EE-xIy=hLAq`EEn@b^w===5Uw7?_8rS!_+W+~{ zDKdFE`;7(&pfV8;WoxwRQ>ySt8$*5!3~XY?#Xr&uZL~6Hs8GBSi&iH7=>J>hJN zpt6cu8oQ}%n!*i{Ct3!X+1afv%KookBX*9~PMpJWA7f5?)YHoTZClBdfb>`x*XT4C zbrA*YV>MSOJo%}&67g*i`B^&I0&fKVzG;*2mXW_u4Ch_2Dv>_7Y5AWF`0w3ly^=n8 zfBhd)!|Xq{i}wXY2|ej!4HlC7UA}K#owb|Zhb{W=#7lf!>2GwujY;2Dgz@PVmdsvU}R^L; ze)?J&=-kETX`sCVFD>3kY-wag$M{VBuO7#J`RuKBaTDC1sPeJE4z=~T{pqe0dI*31 zd%l7asNcC=~Ap%G{@ZAr2iOek=g z^{Fged6WR-f0|BnHS;@1xeR*T)ATvk394yNu)L2q8#a%(q|Km}RHw)}7?9#h#VmbW z^4P6YbabfXv##-f1x!RCTcv|jc8&~?fkK#WSS3{z;;?RqMqGN)PLAAPLqjU6>gXU*rhiQOHI2Il zL_NZ*WYOcN#~!Ei_G~%=GOl{)^%l*a+jdWw{mG9wx?Lycx9rw=G`XCL!hwYTiBu5_K+b*QU{WFEbLWe@IvE0xt6 zIW3TZn{?-E$jOcN_ha8Hl$L5uLt}|DG!(%&!Gflm5Zb#Jt7MGlz%s1!{KmlpcFuu)SmP@-Rry>#ZAW)ARSci?{ zjGfn)M28Lxk%+8NCv*z*-NHgluZE=;$1Z|`iVCDR1^#R*+0%2){v0mhq4}RDPZbqk zk$S`FL$pgz&rE0Yua`W2j8212nOshW3h5cRlPvnhI0n_dD19SEof94G6RW7Wf*J=4 zX?tA-mxzcnbg}*XCb`*;5H0Lf`%+zPGW0T>kZL{F+?Mu!H={sM2=O40dUL&K_amgF zjUEW!TYgFFQ~nXqT!L4|)HD7hPE2y1b$-xOVwWVo;$GdIEqIPX(b%GIJ^i!5m|J7@ z)EFiCz2jkZBm2cbXE@5ps1=@^A2lhbMK&$Ir)B%-=onyYsMoUd?veKD*H{?GjTLXQ zw&dD9%~cf9tA2{;bq6|G9zPlhB>T3CtCa&@g@Et z`v`jW?~(`oD$owt9r1D2>M-#o*|bdLFywtaELwm+i62OxyZbxbUZlVMua%$lxlO+& zAod{r^}pOq))&J*d|;M&$CR}H?_ckI5?jco9~57@q5gPzoG`ndGXd{{%+KRMAa=Pt5W`W&(OX3Zvhyd_-i@` zcmR?8NL9r+a<=;IbYj11CUTS6f*r`bD z=Fz>Li`3KxX>apHE5vkd=>P3U_!F3pgkiP58ve54BjK2@L{3vf?a_i5RaGu-t{m=s z5hmZO*b;nzLnnry--V4Vp_noG9_iI0uBv$-7p8fbUS8F0Yt{g)XJM?fxl0p9AtvPa zj@?N5l-^@@b~=0aHd(Ycq31YwH&T94D8H~NUChy6mOHzDw0VnM^vCq{*RtHB8g>6{ zD*A~k#qM-nq~-hOF7!t_;mF5M`*H6dloBO-vzvW|%x|}-nHE^hRWvSiIVq_vRl2O# zS9P8SsvKP-JxMe_U?n0DIC2*qM>AWtKBZ<5u)SMf>P2`J6qI|jwx~u_P55iQPVZw+ zhzbeE68#`;9xHr6#vsl>L$`u%FNnodvWVTCk8f91Rj|MJ_U&5{!Ijp`McdFvFFdfB zBRv_ia(N4R3!00@CI-)+Jh2~N8YCk>NFcx=e*DW9r9_RjrO1#QEc@}G<>nPRcOCVu zY;h>sv17;bqOT0Cn_YjuKjxdQ0~)+$w(bt3YxLOzR9S&zAY4R5xBX)${ zOxfn)UppFvI5}U`46^!NWiq$2nwg$v3WGC)e1!il&q1agyLP2wNW#KmU}69~NB_*? zz`z}&l+l4Y|JP_m5n1B?9AxAa)=4(XnRjI|V+2wsS@1?DDK(!b5Kd~ z20D|l#aT#QBkmqTvctWW=J(0{(eMl0#Y zqfgP_n`m~b$yYqNulY$LlJs`dQNIZezD$sZR_R7v zMVu5uYjh!R&J!SGzYXfe%jSxWnHd+4lXk0%?2mDadYt6s{Myy^t~1t1F4>h{j)YQY$MHgOW|E&dZJ?>j>U@$(+*DLVdj~Iayr)E3U zdzs1n-aS~Zg2{QuN<7gtx3_aJZka5e?oWiI(RzuUbfwS!L3}(vEBuHQ&P#3{=sH}v zOyn&La&yJTPQa~6ae%9&w)PD8z{Nd32M0#1W`6BJpC+CDe|#d^ zQT#b~$zOj_({Jv+b(@>3D^+dQtOem!K)?k;&xfMlA8iJUBBS%SMncKmhHdjrXqgke zRZ0mfzY@hhWPx{1*}B8XY6Pc92Jf<{16k@_uGf#tmD_YXBf;n?aHb#N+}cT@hT=6M z;!Q1S;bfq|sK%Z`Q1RT!lXv0N>^*o!@bI~#!oD72&$iR^vAx&@MnmZEa1P<;SYK6- zS*q)Rki`||E1d?oB`AFg3JOfL3zIf6#pJ8SE5_y=GRO;a?(WJ%+@kp$zn7CPksf!ItsF zHq_%VU!eK`e2nzOjMPKK8?)~iRi&bc%*{?aA$+G7Uvh5O=Zrvk0nhGEX%10}! zv@>>b+R&;%<%>rFaNkhFtuH7XPFJ0V+n?e9>Gz%YC%Ol`%fAl3*5z8;8j^i26#1M_ z*bgbosn==zX&SI3{|CzO|4>Gge(Zm9n5s@q{2ke{LQ_9|5yhHy?i?mJ@_eUO1W1O( zO}~y4eHDOLz?JCcK|nEEpHE61+_cI54>;^QGO#e)SqxV6Pk;R?Zt>1e>o0gg-WE{T zeg~HqayKGBG)+pY>`%4?@hkU5$ctyK{$vV#kNr<4zhxc*5=S1YbdXl6<(;@%mQhW4 zoaEubqb7awzUD2i!g8;(6#MS|A8jv&;Cfms4`7lgC3LMwC*n}E`&S#~iqe%#@*qddVc)GF}yKUnq6=FNk8U?H*p~;YiW(C zS+h>~`z2t&VdZ>Cnin}`T9j&w)Pwj!#NlYN)|8hI)n}3}i1(2{>~!w0I66A^Qtd@d z#uwgOcJV={Xa73QHo~~;O$ztq0lit@+YzFJq5_Gq%Upy zk6oZYzx*F+0Vp~*eUHUsL%8ssT11sK|8*%k6T46E7oO20qn5wxChZBVRe!#a_g8Rj z2^Ic_I)F8Y|IW2%k?w;-+>N|wY9ZR?7!sat?cUFG8Pe*ugT)0o^2_?MxCJ| zDk9QV*}eiEmp_L_9W}(F^^PJ1dGhFbH zaS-IlTjip3TI?Z9OKzL5u4_Y{mglc}8Ikg@UpdzSX@&4jpLmBCay}D3hMAG>2QM%5 zd@HJ|8ppjn4^Yw5JB`eb@mc5yI;^10kkwo+|IJj}#u74WFTn0%g-aVRdjfjbr!YW^Xz59Iz>k*L$QrBxjYS>uV zh9|FD1;+R_uNNHPrgb}&tF8=GnVK;QH=Mt+|Il;bzmWJs`%QhMH1e3zqoXA<;Nvh_ zFWL&BBt*N*Kh_q~`s?4>Up>5s_G~3Q?7KRQfpH8-xf9)J(0SdM*tdf?9lLZ&2$k-K zy24X!nP5yavE-_^M3ZXM_mRZj#{K_Yh6y%5R7f6};qdq>cG%3qihRe8gA9A8hhr15 zmBcUq`ry9HwDAffEWa+!bAH!Os3qOZH?c{b&lO@Yg?A6#1<2Vn?UwwKN17$CUQKO; znO3x5^<{BS%l0i05|sD(qWw_WZw7ESqy6C3tI~J0jxl>RS9%E=#eB^C`7MB3r}UZK zd~2&=lVhmxF*YH#!-vmnvUcY=*tpYFY7+6HHc1q_cc4@fWam>1mvfqHRLX9IV$&$j z*yHZvWDkleSiFpuXke;=20s5egD@lpB2em2hF&uA6#JmvaLxV9FCU3;%#g&xmRJ}h>NCs>4Zug;#tT}<(!OVZhc)Rd%4lxS7Be*JxC2dtHF_bcVjw_&;4yO%00 zFH@uG`}gw%gEUpsp!6;~EDk?sY6>P1QoU@A?GT)%!%-EKuifdwi80=g7XT2fkN zfk;Y7he)?{gF&ZAvj_p{?gjzrl8!|RNO$+0%e}vQ&pqe+-+RVkIQBrcz`Ne{yw5Y| z{ME^vfB*ys29k5y44&EKjV-G(?zt1IKS+S3U(ETrqKb~&{21l(?6fsB2l$9NDkYMf z_a#I{k-*+CfcC@N+xzTf&2`vf4A0fp))ooyKr198tyf;k)d&|!`Z6-2EZmVBcZ&hI zO`)RLEFn*>KW+otS75VQKNl+cIwdt&^a1JtLTp}LYP0?cv<{%9_SPRiwuit)e9CW+ zZ?ntFT84XxmwLFgLn{cQuQJtDz)7v!f}3cFq#YcDb%ig*HBqwaihJty8KtM^FNSNl zKwS=wqr57b?L&D$uFuZQJf01&S1U~bT>vcG>?>IL=g4!&Z=IZ~Zy?+VV?cX@ExxzW zx&!4G6g?Fvdg_?{#2I@}jQ_rP(HU6ldrO1(q-?2ke-7FA6d$n%5hzo7C1W2zxM?;8 zG=b(7zosydB^sFj(m=~(dU5e^J&UA%e4L-x=>`J0#GOysam8?LCi6_A^b8ERxT%4Y zFPoobWa)@0zd@;ax_slyp&8H!H5lT9Ua2SvW^>( z6c-1r2u-i#I53+wXWK5^!2uS5RlNEAo43^X?U?52E68jCX!fAUlW1=Na% zH%RqA#88L}k_*`X8P8W;fr0F{!JIlaUe9H0Z>&cbT+5*i4-%Q-Z>|D~lFCS*mW~NK zR!PSKel`SG6B81|#l^u5p`F^ogk>(0NwxGd^u@%)bT_V=9Dq>afcF$GdFh5VF_UU) znacFi(%jUPG*~C3CBrlUYT4MGpD63=Q#awOgIgv2Yrx6<~YR{k`*x*y-#f+gu7SJi> zYQvP+=BTqf;v8{~I|aiYa`o>|p4SkU|Avj7X)v=JY!OoVZRkS*Gvx}hc?(GT%a<+C z`3UNvN5FM8?v(8jXcO<=eecC#GOB-kQ%PA7c7v#{X$;p8$T2}x^cfr{hBISU9e;Se znmVTm-95T6j>}QEJD~wIwcuaP7*6Jj1erFmx>N1`p~*kJZE{r=Ji0kGr;P;N1i~H5 zkKa}2#Y-Zs2jwTd%aRj zvj$b?T>=zJcrn0hHTTf~Q_UU7Bo6y7|CRG{nV_{#IXMJ>zV=g8m31yTbXc(VD682H z%8FURR+A6!-FtlRp7h>#@Tr%aZM^P(T~Bf;a2srp<{L-%7yC2TDo<>TRaGr4@}bj! z(kOh@@K1|Teh~|;G5^E*^18;Gk6G!A<~!Qn)=NMpRC64J+c0PpDKmt5dC&Ky$pQ`L z{tJ(eIDs=?CKrv5LlBjT)eQvPIS?xG9qR8l0cQo!W0#lZO(k1pz*%wG_~On^jAOIB zbjnbPt%jo`c$mOD0u>=HF4IyN@Q55CW#;fut*oaps@+*ANe1h|w^)<`d4Sl|uer!2 zhJXBBoN=MD4J^pbRjjnTf=Hr}&{3dSB?q1WO1qQQ%uHtH`jYGA1DhYELqkA<&!@Zc zLP%&>5d+Fv=Yn0bu_&O`k#U*;SY!_RCda+(#}Ye*-)6LthX%dWW7d935Sj_6q02W5 zEqRy~zSijn8B<5cKb;4!GMIfm#fJe4+<=MfqfVW#zH+oDdo`}hMY8Y$+G98aK{vW- z<%wKMPc5x7o8U((=Uag}$Rh+KWgw&%yAvJQ!Di#2T^y3^BKmgUN@k%KrT;nB*lmqK zbGl zA)rzf)iw(@1|t|u0ravO*wknLI4`jNw_o9$n7>(lGH`ZqcqA+&Ocdl_)B&YpR}2pb zms?lKncsW|Ri-FPm52!V=X!d2c%F%`9&#A(e;6ro#uNjK*Vc9qXbLf4JZ885PA7O1 zer=Y~>ERNLID#NRdW4SsXJ2PbjALPGK_RO_iR2OJfPqj6j|rge-zX$jx|yxqJp-e_ zD+U3>s@b{Ooe}##FGvNOS@idH5MzI;`UHPW%RYU&fCvI|1*kZH{f2@_1Mm@(QId`f zjj>*wd70Q1e-B27OH)(vB~3s2w0D>1VD|3S9IeN%^;=wwE~95>X;=`>7>_Y@c3EED z25`j>?CmQ~O8{=kz~l>WJ!JtAC_!f8vP=HdIR}+Pzh(v^&%6NqmA=W%QG`< zP+!2wUoSx}#z!3_T3B)Wx?taU^Qw*6NA)9Rs7U~mhvL@I5RM1_o;quOc~Ov85{rlEQ?b+8+rUEjOozv{iW-URL9$JAKcT2N$;{BArk z`D8tEJtJlsbD#8=C@N`?&JWxHU-0u+i|RN_PbC4={ng~3v4BU4$`KGjD430h65MgI zLs14S_2Jw+7H4o!0eyKzh2zrZ;h~3ClY`l{cN0K<6nAn$ zJ1OZrIQJ{&{em@KZp4o5?4C|wfdUM)e~kg;nDR;>`vMo?f8-`CKJM`M&s(WcajtZR zVY4N{)u4YiKyH8(Dac$S9{0mARDM7{t4&3Y;dSVVPBci=tt~B$EW;J)S35$p58Wy% zDvJEArKj$fw1QCS+|wGY56C#&W2?Ee#kkuZW%gUT8ZWx_PQQ~+V_#a}Bk&o(esb;{ zmILt5XaAx5gF5uj$A6|394dnQ8D!`FUHtK12Eo@emX)P*=T2W8pPUdgDLqH6)f|lj zRScyL{;%66DkY>(v`1unot#hLoJZz_?#V}>P?}SkIgA>4KvoI~xzI@2?~coMy6*XJ zf5Jsd~!9ia&!cS7xvp zc$^P=2j81wXA7&}WYXYTWZ;q>yuC8EgxgR0-ytsLHz8$#ZO1z;2q9f;Q;Y)nEGVdp zT+jH;{UfqI|BK?BZs4=Z7C^=1Xy-sEt*O~f&^;Nb;0&ND)E}EQ~SZv+5J@F+f?ZjPwH(No%U0Zws)k`7mJM zslZ7%lq`bm2L%n4?w+0m$5LSGfv+_vOE)(yI_xWh;_s(OpgH=Q=2l83bm=0ZA~)@i z-Q0dak&EXkE2sF)SZ4fT#njpwF(L1{!=cfRn|9WzfRS{Q-e#)ke;302|f4kzi=+&5CjD$$_}nd{R=iZT!u5+ zwAdNLu`2{l6el4W$3|s!J0Nnre$vHJ`y=RIy5rk z;H58ODZ7;<7CwTjK7aRkwZaUalDpjlRHgsoH|1sKj>IA;bcrzqn71)`x-Qh6Jl6fg zn0Sg`y9EQ>2jHnuJN``d{&b$J7b1HGxkHK~Q2rqA5F^ zFo`wJKOn=@YU-Ce;9F))X5eUxjZ6S060bdr1U&)=A759`5%20%D4U2G)%8J<$<*xQ zbJaw*jTxHa6z^tm=4v+Lwcea*%8=L5(<4!$#4hYf#0OZ|>c5tk#UZlE-S5zah5ipP zk{?KE1kl^P`1_-ys^oXvcqzI@MxwmLB`Ed{s%&S z{orLd%Ti^LCYOvR1Aj`h){r#d@xX&me~WJMhDr8@in@BX!WT4}hxfsQhfnr_F^@n5 zfp;J$jVD`_+rFEZmo)ss=fc-$60o$wXRY7x-BQpr*sc8B@dvD?m8yt^1OXM$d?*ax z3^l;(K;U4e&_U<~{E#3{pcb#>)Og1b{5+}>*+_+R9>A0x#{2)bc)-R+{{KTfAUrmU z`^Q+(&S?rJhw3tF&f#8_&Xrtu0*Ze|>#fdPojv;novF9}T6Gm%C?Jh(vv8)6_PQu3 zMT%3CfxVyAwmy=355t8G69COvZc#xDuMP`hc7}A3?GG<(z64uZe*we+KxObahQq1Q z9SozcRrC;w)ia}JxYzfz;pa-m@vFgXr?*--v8Am8j0OQ~fWd`>Ftbf#Ln0jqy$da>tUo?Uh=12I)@E4iDMou80qPGYsazhBJ6oQbg1**Va_K`u>A#6 z@{AMxL$8BE@_p0y23%qnK8tUX(u0E^u#draN+Kv3?5<&haGlBjyv4^sn%8Nt{|~CB z|5HB6pYQ%!!%8waBOHz6^JGN8>U3bN#Xi>%O}1BZeZU~F)4-4W$N0F1*SCD``xwur zpjS_xCK;xsm0<=yyqAXgbUT`plY?Vdd!z*h)Q4Zn$_}ES*`v5aA0z^yr+!?h`Mc34 zqm~76j*`@&FlDYtx$ks`#9jed{p*`_OyOZ60PF4P+QFp+-p5j3V}E@60f>J@RMf8Z zT|U!g2%W3i8GubmHTxyND$ZRW%^q`8|DKlO-`Lz;%*_Y)BMpH=40Lo*9FSb^#hv0y z_AAQ(^d5Yyuo>OFyu8#>v4qUVijJX2T}&_te$O%G`r+JI%YPh|+jja|UM&6?Y+wKY zB@)J;fp=o=C{&g|Pm|lVi2jds8C!BNYg9xYZU;-Tu&@jfet62P(|V&!)ddzL2gZ+t z2tw1-#zI;Qf%##*w3`)GA-uz8W9I{Lo7_*H=yUq|WNYi`EyRRtMjReGM2gG9PzGc# z>UP)a**IvKR3AQk^vHmRP^A|95kf_0U0qIx*F%d#V7m-ytoeZRrE zERcfNju`>`LQo_*&NMR%2u#3u4)&4a>^lgOg_YT)lWT=(vTLOPnduLSNJ#}gTm3{z z7RqTFq6PsD&0S_W=0MDyh_9xgh=s1V#B}0EO#{_`aRDmLPlVOgV-+pVRyck$%&%wT z0cg?El2%%(82^eu`pv7!ga%(38NA#zYel=Ej5Y<3^zl*6?%BRtBbGy7JM)1f#x4_k zz(`)J_rgZO$Z?DeDZ=9n0ul}*3yD&f(^EJ=8yehK`WGZP{vrz!35kgT2>f4GED!G} zL|S-VWg;GD$}5R`YzZd3Iu&K+?Ga#TGcXJCItae1aV(k6+xC!p%tBAk%NvdLPF2nt z&$n+|D>LVr0~f9Rqm;P(mqL#Ht!-^3CK!j`+ay(+Ag$pD=W*pPNX>1IpzG&Il1T$h zGEvyy;vIp@$xe;c(lz`mku`6g0Td4f;iXkU^a@ZJooY;D##6fgGicE8KY<1uoVqt} z-R!Qy_s}$sfj7r6*nU?P^@CYl*g<$H=Nb$c!x+`5b8iwzL%9YNG6wnsR^86b)Kq;| zPW9Y3A0Q4@FYcuN>FvgvX_%)}Z7$M55$F&Kg&MRM2ps zjf|9o-oYEjW`xpk-~$X(LOJe!J`do2I!>lRFcXJjs=tE) zgxtWO-1quib2^(7&Zxd@Y*a1}lwgquhjR)VU_yHl_ctdedkH_2XIoKT9tF3RzQyJ~ z*y1RJF`dD~ZI!VyUd1E8)D_)CcK1erum8^0mP*;?4lsOSt(W(I`4T)#-12f-Wv-E& zV`ocBz}^3vn25+w)sbtt6b7M+6aoW%iMF^)Yh>x!YbOeK`fM>%t91^8L8w7|Q+Mpm7Z(;-R z4RG#3L!DaZu7O8c4Yq9V?n&V#`UCBHP(4?oG$7Gdj)&Q;ZPsNsu#1j%f?8 zA*G-=ZKY$X+1#YVx921!CG`@?l3$`~beQ7`#Lqr>>+hK|2Say|-^OPJhlHR|1~ffyr{4no7R+_d z@@LEWKEU)C1?mkQaq3~Ef2^2bzi6y1fd2w8A}J!SA43LKk4AL4RDi_Jb)JL)_-?A2 zscy>4XhV-Uu~{t{p!_s&tp=jCelvAO3Zek3Hdd^8hUs>m740sY=yWbL4E6ME0y0gN zPTQ?wr(?Gbir84-dGf4Ps;R4Y-hv~T=RV(wsC0LokZ-KB&2CF!@Fw>`ea%HxM$U&% zQUL|An40CFYo6V7cHAps9Q^Fz1)Bii4`3}YJ}!CkXVa{F!^rr*wAH!>y&jY?-KZ%$MvC{~n<)>>gm`RL zRErF60#ki7XE1v#&uC6oiZ3baO(&}!F4&nihl%`kw9QdgT8U!UfOKw9G1!DytflJK5Kj{DT&j?`R<9k?mEf4>D-RDDQWU%g+M6f0 z_5p8PShvbTk5uYF8EgryBmg6*AM8{7d&gTNo1N-lB?`c*p0~&zpS6t*9tFQDJ;H1_ zKhlekD7bWV{8XS0`t<#VDE6FeMNRQpu$F{Q3Fv_b2M3_hCU*nq8`v*8gKbuAy*-kI z((OaH$FM>ht+$N?(j(r5ox+$y6H#$dsGUOwTU|%?gU6>wp?Yom-GSPkDKuE_dg57c zZ!=(w9D$8v1fF9ms-(=!_STU_Zv!bATd)gykc+r+>sImJ&8HR?n*nM(bL}x13aemi zJ-pb@W3xI6A$`9)RBq9$>c6#c6|VZPHxIj+>O!uL6p#Iv|A+ADvumil&altRzc(Hj ze{rI&NpF5Gqh@`38$4|QthkE@;ZBu1b7#G$)8qgD;)JdVJwk^mcVa$1J~3ahfID1u zVK@H3VeaJW=>J}upqJNsIgk7meww;;*KWume*|JD_1nw?WNUk~vE?Z(gNn*|!~*{* z1pEHK;JRLp{(Du)KiGeTgxtXM+&aeXB9ewk2$&WCATN1GV7!R#S7aoSv|#-S#WE-v zK^4ccG6N7GRy+FL{3+0=21z+DVNOin9)7Q!z2J7itGT87WeKoe4VIQ5^`a>tiBS6a z+mEL9#?S8zx;fIb_ebkxtGL92r(@`4ayQV_j?_(leFH3HZ#j!+dUhW-wqX$1?CTYf zO!1ndXrt7{!J!;%<2UR+jp866_3~3z3`0G$CtlIxmtD%0;a$!;e@2so@)IA2o)-&|39Ttm(yn_fvJO3|Bb8B^2L(Xj${9Wcd)O4!epzAQCYf3A}pPH`ub+15dY?Crh1I4~p; z6s(7bvM^HprMb7wTa_wAy3$+$+`sP89;=snzuxj)!lOw5VTOa99qeHs4DK zd}YuL9RY*Ora*G7vul?xi-<~g0{2I*je~=uiFo_MMo-M2n^{8) zDum4M{0No`Am^0isdQ3ZeuCt7-n1fK)m|!Tfr8MBmTp86kDXKkatUH$#-0v-fEg7d zIDLWdh>4l{;U9l~KVV>pg;~|E7jrOj*r*z3+7y(f*$)Q9WDUNIg4lR4)GI5COiT<84<|+> zeY^9fX*hrAe82G?bv-jpkED1UKi{KA2VhY?Z!CM&{g-Qn4OSz4MCtVA?OO{d8M6cgU)*(IAG!A4D|7%P5>*D z`%FLAF%YRLzDH4OLUh!vQ_&O?6YV8JZF0YJg3h-A#rEd=x%*m)HmkH)A@HTY|7K%j zQ>CjmbTZ21bKKRh=`V-rJ`gU6r1%j;L|dPOr?7R+A=EXz%G;ZHOG!F#nEd8dPa)O3 zt=mDYalECW0b1HnFR(M}cTb_KfQU;l(LzXr4=xniw(LeGCK4#?9dfD8Q5|3<(miYj zeh~l=kYC{a(%Uzj9`LCHX#TA;fT0c_hMZ@cQ=aAIS)hc3VgwpQ&XU||yNoqCB575p zQ7Hiva3yTyY~Th}6~F{;7u-nwbwP{}Q=S0KR;ehqNNgxyUSD6F>!}jY?CXDl<79F( zr>)--pqMG$-*@&R*2fN<{^cKI3=T^nU^%?zg=SR)63( zI1VY1KyL3i%p!3?-|*oE+-}$0gF{JCnNV6Q@r&2c&iCz0E&{KH>%MrAhMKgG>OMu= z_D@X2HBIYiYg=gwn^)yb0iyAo@3;eSnD6)h!ahDiX3s(fq|}NV4UER$`}p_(^1(cQ;Q-x%qn zL`1epm{ugxQPMI(8#G=?_0=B$SP)N3iii9udK zrB*fvPxkjqxIhTrx7{dZbUaOeYJXoqg_VW%XnUcaHqUDtJC zLEto@4Uk4$w|v>(e1BTrIy~f=EL@){HVSuoZACa(S~jt}Pyd!n_0Q>zQLDC&9ae!Y zyUOzya0t5BYR-ynR-z7FBO2@?es|o8ZAFiNrAZi}CY46#xt^WArc*FEoDS4_5qM~I zNY3_-z_T7WChLrIBSl8-MZY5=B0@qkSmu10zMJEi8u1GPxVg59C^K!BpE? zg3!~2@v81Epa=t}D}Q+T*#(@P1~1`5UIo7ogRKOYI3G z`;OAYsqfQcvzAWJ58s}2sLPxnA|sow>YjUq@TU3|$*KpJ5GZi?a7|ksR^0x^bdK6R zlU)mIE_BE5t&zKAfw#Gk;G1q*!dkGe&g%2#B4WeNUz|<|l_EK7GM*0_9PwBd_u-xl z1*S;3Fo?QpX^L|?9iH1ct2`TbG%bO82yO7(xCO#G?tuqkH>ZZ{idtuvz>!niK*zR} z?GgvgE#HNN({g`q+iWkGzevmSFmHKYZ|xxG*1nB60n{6>AG~vYDl2*OP)=scQ>Y+B5!F`U>$I5kfZ{q*}k@T(jLqv z`;c8}Y@bG&^*T>6IwT^q-w+*lKKl{*Y2-v~P;|67>gG*`s4N&lPL_KZUwg;*DCZ$f z0^?Zu=7DvdZ2T*~+w47hC68p2feu#XxC@~ARMp;i(XcU)JYT>`B*u6z%qvAaX7lF{ zZ&g#rfU-AviU4u|g&)=_*xIa5Fkk$KN_Y-r{nvUiMc^&R9wb9pFU*BFG za|8oAah%C8Iv6I?)BAaOiAad?0a=@h=x#yd(0;`#5=nhl(%C>M3*m^30vrV0pcft* z&e)0Q(4DY7JE?zhZO@K(yf>HOK-ar|YJRkA3*@SmbX@oDO#!x!+!ZFa*D zMS473T&>NTeqFWQ>HM%Rhp|2?nI4%4E*~A4VX!%|SsPb-_3BkkJ*;#Od@^AaGVcjJ zn>am!sJpyqDT-t^qu7x=y_*GN!+3R=rItJqi`IuF;|>LorALpzvMymaY!DLajkT+rddFM=sXvmj zY@iqg;n88y*MS@r*X?FRs3`kJyeE^Ah^}#Z({%ShpXvtBt_uwX@@>KJW8$fIT&NlI z3h#$N^93ZEmR2;e`wp+&8%WCtrja5r#DRJdm;3JjXiJ6!@q{8`|GnZ z{)>0ZQ_oQ(zwuXfe`F%Y&+@x1Ic-4oZH;eskW^$-KXw% z>yyur+;EcC4)xqjSKfb2;^z?#?kxKUb&zfJD6|zQmXC39DPf`r6Z@rsL%hF-z|n^& zP@r0`%QT}SDmKVmFpeK4Ou)6mT8OiD@s8=gW;;Nz#mf>AncFX>=WV2*W_C*h$@vbC zExU$S0-zE4d}ovMq=P(tee$~cC_DXzn29>6{*AbKLp_l({3qj=FEW_}BbC84BNcbguAa|-|NZOJ zB4xE2S}8J75%G6J*-G-ajNWGajx?>R&Q0A?84n-@0X*7lBsV7($EFYMtIX%3SNWARaV) z_iBAmtojih9eR5{F^h*r5E%*;R%lwAa`n|dFjaaHKw35r0pD55)>PpDtm-q(%U*w{ z#&Y{QgW35C)$yvQD)t^9KW_W^Ve=+ibYaA?t2vS5+jK1rLcksj`rv(rt1NQ{2 zW*hqbckYX8lw7*Y`p8x$ePC&}XqVgBt&}BLnub?MP4LgoF2dkbwF*NA$FVoKxZ63J zWZCy74_H02tr%+$>0{7lBUySuq?(;3Xsm$C_m zjBVtb0(6p+Kr_c60JuFky2^(r^yy=)Mk~T$vH@d_4I9s1>Fm>zz>E6n^0K`$vzy-& zUj!&-4J2bFQtyTDk=0vYfY*rq$R-3K>YvZfo#OrbwR6Yh`TvN#Ah=2UU;5L!ZS%iO zr^&U?|0?FcPu%-g9RI#XkmJ(++7cML&Ub75HFJJN?T@YDihL)9)0cTB}pJdtV za#^gFzk?s=J?61pegftheN7bi6T;P_5(gJBGwXD`Tz{@Ve(d&Fld`c@7-H=2+xhk* zgI@xSaq+KbOOiJ+@t=5K#r8kWFcBMK)IOgN{~%rjj?9R|JmpLi>;0Rte`ICve*zh; z7_d;UW_=d!7|AS2F%)-9v31JG{?VnB`aH9>>SB5go^FF8)VHi|5ELL;&>bA;tSr2m zy5K(?2!i~_Pcc2G!M$E08U6hNrWZfe<{HkXC#zq-cI^Q&MslzZk0s5o;Vp?@_&kas zRJ2Uhly$C*{`m*yr;IrRa%F0!Yb)(0e`OU5=L}Y;nI?xV3JD8y$8q-DX1R6^K9;yR*V-*7%IYzDNsd?(Aj@GQGb9n3A+x~!e--A_cG4V9Wt z&EZBeAcOQbe&u-2j~}g){B-^P;{}C!iOtxcFELR!Q1WQ_H`g&|Xi9Q2*0=kYLq&y9 zm#Q>}^C=Z_OquVNoxMK~Q}q(jo1!QfmA1L;cTc@L3<++V9cQ1){_yG2qmMUH-v$>- z*7mm`Fsm4|ob@n$On0q6V{mM56!bb0C=N8k+469&o+J}gFue;85=i~S(yo?ZvRLOM zxHYxGhz4*C(-JteDR^8@of3;i+NZ*u{;0OTQo)iN8Oq6U$;!^APGHrO%u%aI&MfqU zCcSmq@*pxY@_GDE!y-ay6iWN~^S=-~i~UEw?izNy6flx(zajPmrVuvhn6~Mw7oYRT zHARQnbK5W85Xwa|F|Ex&)`Rup?K?M)*Y5{{YzXKN;ln2T>j)rl+)Ds2j@R;X)KoMQ z=*MGI@9e@q18fUK)CCd}>R9&#F1J>bZ3(Od;Lg+Oo`andJCe`{%|J8^My z6C?(Gg=`$2^?z$2xg+=XpbzOYWJhEjSo@E^i;q-BB-};9Dth%Wsj!TOIRAlGQ zDgQNm`>Q|0(Z-A4FhsANi*xSe<%ks5>x>1eGw7sv?bgr1>m2j-%9PJ|9uK2YP=U}R zte5+|y`Y2`?Q6MN>vIv8U#-TJ50N*#4945nZa;bR%9JtK|G??Aw4xEw4R1Y@Ms@VC z9@s6NOL642fY5hn!QJ~!#SQUQf-Z(LqBhI4uSjVuiuE=MLOX6;%fm@2I% z8zk6?Y88J=Nx{LnXp|0C7+IMUbI=Z3R*?CflCwPt_i{O1prO7^6!gqYv0VER;yO7; z=1rlj?g0X6U0wNCuL!o%`*n3V)WLrOJJ=IA*e8l>H2HH;MpvNkBGrsiD6r-%T5}`6ywu^xRC%5{@Wy`mLWul=(_FO&?e1S6zOPu2UGtE?Q6#)+ z@IdUpxBw~zlf6>V#a9dY-ZCf}G36Rlcb&e@NX$x#y$RoUur2V2<{_;foS53$`swWA zo-@$NA*AOz)!-pZ8}=?`Ov}%QDNHb-621J1aP|8fxkpvDJeIrHunY!!Gx07FnuOML z(eO4X&j+Fm6|z*Z6S+vE4v5?_H#g66`9l;uH%OQdMRdJeOMJbDPs6V-1S5BLJ_pBWuELDXZkl#pi2d0eASHqe{kHcnV4( zW1Z^P726336u?WSLef|;mPzxm3OxrdG~<MpB-ai z4Vx1eYu5IvT1BKkY=GZSFTk6V>`Lur%I@2+*(N10arXU!6z>MA3(P**`^n$(Xlv_6 zb?B~{_QOO6#^dDdhJAo=m7aa)PwDMX5+eo*f_#Poy~emtLI4R52c(ou)_#IuXhLtu z?iNI3J^|P%JRqBL-ibb8yP)@OXlT%@Dypbp3SI<`j@F{I^AtItKquC4(kvKOmhKC7J&TEo!VXV3 z&=M%q5-x@5eg`nT^vB0alw#3J$QvU|C8Ixsz4X(SIaY?f z1e>yV%xch0eQusnULq;P&U=B7WUHukHM2^=8|j|| zYT+jfDKr)8(P}6CJzxD@2gosJm$rUBn{`s3DQa*0i=V-+B0)=cERd><_gwA1;hQAg z->>Q&y)q94Do$DWw2NQ!%T^o<>U5 zLE)~9+rwr-(he#IeVj{|uKxWI3>3mEwNm#Z_yGEeg88I*X|PVnNGf|yk#*LWIcxnGP2DaZMT zyC@Hr1yA{!)$uDnG@0coll&o6!I{MkMl*aY%46b(lm)J`@yO=mNv)Sz&5e$n(Xt+W z{M>wL{A8}{x)P>mQL`DLmcLuk0>#(;N#7pnk1HV$7cuiOj_w>hMuVqffvTU{+-xFOOK4e{2R3&}0>vT)3q|97bzYuj_=d&X2>S>;RH8Ea3qFlQXTx~>f zno+o}##yd{OP6JBjcK>e(e9ED|2C^<)N)jn$!%aAslsfpj|ufIw60P z+RIcl3o4ga{}~w6$&`>PI^Gxi*%OD7 z;x#4>Giv5xaIgqilWpJjfh&yYH$6EoSGc;U7CP=ukF;-(tiLKq3)8St zFSHT-p0M^{Jn^HevMS@Ged9R!X>(Q4{YzxQza%K|Z{B!WeQ>Y&h-~(LNsjaHzQxI> zIEW-_lEKq?WI|KiRh?>~$3d&ZfeYNa10J4H9k&fijCWG<_g>T|3;uV zV2W~XpK1Sy&9fy=N+QEA;M(beLf#&}C^|+xYL16@Gfa9QJeD@JC9_DkcH#K_~&DAaY9T#@;gVgnAm#&Ijx?BDaqDRJg} zWBKvhBG2tFm>GNJrBE3OhO$5rFV5_o&+%w{zb0_z=4;}sUAny{oQ86(BUA!?7c093 zO>+1YXz_C+IzF-+A+oNjKvN5lglY7tVj@|F3g+v3nlC4g)zZ<2(cUa}Yznnn=o7J>kos*PeC-hp zGE34a`R-ln)=`1Fp#t>o$;F_ZPlev|#g7LR;`o?ujAU_~mLXqiyHWAI=Gmh%v3Snb z8>*1@E{V!Wjd4Qkp`fZki*m)ubDdo*Rb3ch2ASEr zAKc*9a1nJ{Djn?G(2Vbwh|03Sn(IqYS zyq;UL<&dXnZ26>g&L@c_jg_v7nyyOs&BW&ydg+U2hcq-%Ki)Q>nY=FTX5CHrX6Gn} znr3apX&!~!i}pV`sT-L=#dJTgs!)35T^@pp>21kZ91awanc2uSEQCIeE2Y4s`^E2C zPTQX(i;Pz8vj|^a-0|#HOh(VNW@uW{Z0JY4?xGFy?W%GzU0&k6y`M$5HT?Pe;YmAp zhA?x@WKsG1v9w1@N*t!_w4IYuVa&2Zh-B1`X7N>$W2YmHK8!0X? zb6wHJJ?m5vHKVZCoL}=TFzk^jbQjv>p%7`W;xcT`lCC!sYv&;k`I725)e@G?l4}?f z;$=D?zqeXLJ1H9%>yRZmD$nXx#U?e*z}Y#iNz}dXoi}DN&0(?^<6_XHuC5sr-DW+` zKd@Zoy#Ev}s+7^|E1yk_kbd`dE>y2|=6u)$hx-mmB(sGoYHcd7H&%j;)SWYsk<2!@ z;b@3CH0?#nqFK0*gguM-hHbY%CP5&%ZpVVE3A2yzc+iNYqGxRh>iX^AC|hn3>Vk1j zNe$OYTq#4=UL&7Q96#5G=07F61x@`)EyQ}fE5H7*8}ltdXQ(iG@<%CHbDaJpv0Ms@I+`TbR|WtO`J4N;dre1B|d_%g+c zra(pf+fl=C((`!dCA`;bn%?zmUdxXkOl{?8dCh;gLRD3+wp*#rl4jk@ffAu`YZpc?b_8>LIYg9LuQ3+F-cYOoko zjvV*70 z)!L>nnH(yI3lnq?EpdF?~ z^HHyoKy{*Ur;T)~D*n>IXVhXtCRfrHju^bddYp1>I3(oV)Q zra&mo;0$5feQ?hHz0S~g+@*^FcZ{!pRw`Z#)0id+bv+W4LHEuAis{l>zIl#Yt+!zx>%b%Vwm>+)>+VF@Nv=1 zPX-ZCbR8`FmACMik!yv$%$g1DX(sI|{Du0`DhK3cmDwBw#f~Kt3*m&x>Y*6lP9rlN_>xV*R zeWA-uaq?xV(b1j#TAss|O!1sv6p<9=Z)1K=-3^Z1`8E3VH!WW|$56+Mu4v^Fbk$7L zPd*%b@uRZ?mfd5izqT(S5L68sje{Et28RSfD+fLAJ@pxUg558|2wqDxT|Q-QaHvkxWat%AzGoD9F^LgXo~R^fz5 zb*3$O)>sp}cAY*&`gmIuzO;XvI67?x=SKLPu2YG_N9SeKUBphi!Oyp@%Wj$Jzw3raO=T1=!)(LE^kf1ybI61L9Y322)ucf=P}u70pdiDC3}<+t~cO}t6e zJ$~Uje@81V1>zzOH|86!v*WPa^NMoSMb^-&mi7_bSCXhPZqEJD96~?NKP7d}0bSsc zg8Dh>r>QK)^XD*MtCLTiX{UMSUH5x9be&YqAE7&vnM2*ZRx5>gblt}1-i7-DC!i;o zydml1ya*1Wp+Q-m@%7R2t=sc=k-DB?ih05oYfl!(1iPjm-N7UD<*h1TqD}v%VxzvB zUZ9X#7+tmY=G7X->yin@qY<9-C@YMjuc9$eFlFhP$XJ(_?q6AGVFS-=Z2NasEzC<* z^+Xfu9XksO+^@SRoE0lEpK=I6U4Nw{T)Z?!v_GHkNQaC=tay25&~qgfu14quyG+Tw zt32!~CEgnxNN$Zq73(G`4zCcBG(4n?g4bfjr9K|>FE3Dpc?{fjYI1dwUIm2G(%x&S zopoDTpnK+?_$o;+&w+StK&o12Z2Hb6nVdyVr>a!4PL2cVt%ik8-S+OM|x9`gJd z;S$I2@(s%A#j&y2kT+6(B@MQ@ZnR6QBkz=t1BQ)#0@#S8#|sFFwitZxYZ&P9> z1$i|FawU!F3nFQPhTQ7fRP<@7Tc4xs^~eq?WR*&+s_Vl`2}a=|f_!gbXJy*$hR# zSB7`BeH)M#q{poeL%CNRIN>0u8ac~;I?H|Y^$NL%hrBA?=kbtq?KN4tu>lAFA!o^C zG;j4Mm;Jk##fgiY{<)$*ii>JI3oE%`o?*hKxstwfzRFQ3vKlm7e;>f2=&x{su5OzDQ@ruA}o$esnK7ik0Yit=NHIr{X1%JxgQ<9j?`W zD;v3r^no%{jI{q3ckcn!bkpvO`dVKr#sWyyD7`6Fx?t!{dIzO#SB4a0f~D(4`Y} zAqPK_3Rl$EjM05>_ir3RYO+U~t_ED^gbZ{$hbr~!Dm~h9t8&kajzjL1GqE7Arlrz{ z1##~7y-wy>a9a6VA?P+mP(fO&nSYgwznvz1=;%6!7`Ib$R0`@e%i){4>V|1}8~x#T zNekk{{&9BJa?5iCL!TF8zD`D20f^xZpztU)&DJFj)7_2R;Tn?p;&5m~79w=E*?(8d zpqVcH^Zls_IHSmDo00qa4VtoR;fft`aN7sLa^W5euf30(mZb{ zsij{cDGw8N+)a>rLft_sd)_V((>BQf?%rGfdjzPnHJV9i3n%WhH>`YDeKm#EdO(oX+dZ z;qY5?Y-C6o9D7)!@J8MzmInUF*!k`i6$KbqT6h9h@AoWitNdg7^sj%jK(;wXXzNnZ z3RzYVNLJ3F&rLnW2~=2WHS_a}vOOPQ(W$eZ`n*Z$&_bBg@s_oK+RT+6$D(*C*fw>q zUHH~gYRU?go1ZYWz}7C9hXnX{-+@>K;4>2TUi^q<2X$w^FQBR=#9;}RF;aNx(04440pz>HvhJ;>X*HqWZBf+SL*=Adx)*w z{m{ir83%8jm3x#-8z&d^)s3&GS3WRI7QT#4;_eNu-pdxX^1_84Z#4-~ zQV;wJ-@{Gi?K^Cumdtsz0)Yl@P$3B;IEha->Po}zS&n}>p&Yg~3&vn@ecpT937(ra zI|v$~Zie(teQU35LW8TG-@rg1VOGQXkx(k8B&abXdy6`UF8#fu^TuC+;X!bV`J06} z+&e_J=jQOEwzc^BLauK?jE}}hS4TW(^PJ4H6TXJ7k3Dp%LtTD-m?X))tebRm?eKUa zU4k~neHY)ejOl$D&@<{AMm@9vn}6dNFHpe8i6o^mp@(kO(eP3x!9S$aH_2ThL3%5B z;b|s3bF{MxoCRtwh3mzH)gF<^!Ixv7P*}EX9qcEuMa)Qs2R0BoE3|G_7j~B!1LJFn z>A4lH#Z%e}*%^c1n+N?0toEV&1Zp zm@@bx4d{SHM<~KHdFhK7M_NjYdRnEgEk=ySr452*eJ5`PY$f|&Iylj4ciQt53P0i* zTadJxoKKXPvGUa;Z>HRMKp7TN6}2Dq=~GoZgkM=kCBF!sSvIZrDS^L#Sg_*>F6)ZO z)R&(E(wkO{}#~bBdGdI=zmAuAx@uqT`fx0Tq*Xx=!L5!TeC&$+sWVP2E9FTT* zZVim;A1k26wORWqIby?TbTq4k>jV}X^qB69g-XuMZY3r+d-jV>u1@{nw*grGFzTM- z;@G_?`IbwX9$RLW-$V{nBw)AgIMT52AZ=G!c8u4dsDg$^XfxyCYF#b*HZ5zkx|%F7 zFZ{REpTv;T&UtLy(|5{jHk&)C7MFyfaldVOQ?+c4rsNVoXpvzQ_ILc=m`W!V7ME>3 zw%-VN0rgl}Dbm_YYw@=bds#50Bp#dQVTIj+kF1-bjh>nNQ z8)zQmV1q}$Z$iSQ_`=Kkd<)}IQ=T*&p|MK9_#AdwoB#N??YvzlK_t?;eD-mSW!L-Q z&c}RUxw!FWJs-PFVY?dye_b0}Zdl_NSF-bJ8ZzQ{D&$DjS|vyHaR7VFNo9PPPV0La zmwN4(qWZ@UxItKTA+oTcoahPBzFx0Q?c6?B!(xr|3Dh?>HK%ypX$!Bvs(%tw+Y-zCvep7G-;X2oNtnq6&b&dC8CY`l5D=xe6od||R z-D=EnGT3V&p%fq-z6Ax!n$qvj+CsmifXJZBi3j4+7<~}@Vddl;gIztIzUg14n!VUkV{YiJG4Jp#=0OGU1MlPrQ*JZ}478aw z+@ZQ>w>2nJ=9T-UK#%mkqcq*KGXQl^k-L|kgKl8cGF@@+Hk4ixA(o{yi6Vk2YPFd z#)nD4qD=d(?obwHg$nW9RvlfI{pqs!m8g)!;!m-jb1C1yy^vB|T0JOUYeLs+=4kpI}6y3#Z6=jie8%0V+wQ?OY^%i_(}h z#KwH*9Hx)4{hFqfn?g{sA!Da}hH}erMnGvL`XX9lB^Op$shqEI~J#Pdw2{o13 z@@$ts@tbh6K)jE9m5Up_D{iF>w0hw8;CzOxJ$-{V23c$@anf{(X5s~D8j_WEh(at| z|8e+PBdGexky@X7p2MQrmx0dfBwUaPfI+z1UE^ZLTg`p=3iZyGG#(*{k@X97SbYm! zI8u4opEQxfWDsXL@IXwA6FRucUKesfPwGza&72`+eSPN&j_9PMd=TYOsApNC7vet* zl$-J3j_0~AfA`P#@bDN#$$U3+tftavu6a9gviD!ZZtqai=p2Ec}!0diRo%l z_8%+YReTlUv|aV8zmaCW;aN!Ecb!U=MVBCcs#LYvNO%gJ=^z{W?ojGgLO#kt4#tmS zPcf)AF5Z_Hn@FW#6Z~?n!{QFUR?%m_`04~Jmx`VpAEDDQ-8*nir&4Lb5l4IFMSz2w z5C|Ix47*_V?9hR8+*pY&6~zxaQq5wrEsPYPGGY&8h^WpoGSAhibq+LYcLYG2VY*TQT7j}sx?JEggoF7<9y?hf znw-;G(E-O^6wJK6!=qw$)*(EQ*Y0+T04Tjz?Im)dYlC6u?5N#~UT;J9-&%fh^JK3_ zfy{5rmyJ=y3}z2p38%X1b-2*UV<6XOH^DS)VWPq$#R~O{s51BcX>A{vDuhr{{5)jt zpHzvgXuxf5G8f-0COl6cG5k6b4I@iJ2=2sL5F)NNU;yU%x}+c-^TW_Lzs_IT`W2hu z!@|Ot-|j*sope$B-NTX8p6|^YfhSQwSQxe7f+3-TP+Mv#_mZtlLzGXdtdH!zs5C#E zO|-hepcI0onzsK%?;_Cp;7Y94^6XWigA0*q8q7JOv?^Yyanax32%y#oHwBYf7so!m z6e+1T;}YE}w?O)cS*3@x%~Ffd3;b4!TQBqfWr*zYoA-i-F}G@HvyD}%mRRJj4z*oR zJn?jKR#y+*0AR$E;c#s%ilY#l4X5&uWAw|r(IO46cxdibk3>%IxlV*R83w5>lKb* z_*J6$lYXH@P%D2_E-&9y{4dS>zt4A3uU_eD^b;#vf*qGMY%FnE-(lh5S`z&fIyVOe z1?j#O_%8*nDXXXikLV=1?zE0H{?xT^h>1uz_6`q7__ApCY(u22JT4urr}hE(Qe=g@UePygmH@(M=xwipEGfGi$Q5M@C) zYBFU|)Z4QxISrQlJON^X_E$lv4{$bZR9zyWvDhj-{p9zGx4@gz2#TnKQqqC1{=_ij z&w?n0xX}#revoYlVrzMbX$=>UkPUD*CG&*`xNVL6DYs0@Do^=oWQ zOQE9uGv8y%A(bKca=3!;)093JHtN%X>ahX&R!;|rcuR&-WFc3kJP8elp``iF8{<6I z$O6lb_ov_aU7Z+va+UYE@fEH}AEdH?(y2akC7{p|a z+W#p};Qy1t|6e{r;%Xv)eCyx*jVg6MEZ$H#4ev9|G!9$m=B@@vL+$Q zaB(phtA?YehY$>=ladOjx^W5e`f2i^ran7TofVW01F!E*G~@r0ZSrMezcxA|RvyL| zdQTD7EcT8~h`H$-9Nu{JnjfOzy^BBGxZ0>CPACT0M4$`sA4MLe5tc>i3Br>aN+chA)Pe<+d}167~vYt4Pb| z#ckLb1o%pFHHB$hw*$CBo7#<-IK|J+y>EL}s#786Dyiz2SBh_o6up{pk$xxZ_(q1x zuxH3b7@=?Cv?lOd!XuNpwRp7m%4DtByEo88?rj?Ao0?y+)+G$w;@Ok|tlVhN9>j1? z59I6gX}mD;m%y(49L`_T8n)miJsiu%-M_(ew1S``&bYJd{wz zZujJdy8m#KM^W_$_zfxnS&)IUK@q2r8TpvRMkn9Z*Z7Ng@9a1DS1;d^MYkH;8L-<~a<)=fsHCZ>={GDt3&w5z2i=Lo3fp>Mn#(0}vm0m2;RF zTU^r5!OaD`!=js3TMyU{dLg`t5S9r4R_0u3<+!hM6iJeD6imhaSbyuCWHYImL?L=gxs3VQ$J!@UFWZg09cqIS zbJ8uQU|WUcpRVqiBA(q`Y)sUU?aWe|VhU$ahwXg(U*SC<<6sc5ULv|yTsC?}#l_z3 ze13KiEw!1n)9Gy#AGPM70Trvy$fzh_BAuGPmszSRESvwF9XTCAI$EV>O zRxm{#N!>iJE>)=Sm@bx4a{t-a?y#3MaW?i|uVeqyl+6 zug146VH`o)Sb_kGfd*6ki%FGXY2IlLbC4onR-t*Ba1LzhgiEFI&t^nJ%k$Q2_jTOA zwQkt_Jjb7|bcr-j0T#PDo>(tybxrAqIgJ+gqJ|-yWj4UM*!r^_^9PK{*~NDgCdE@0 ze8Uq)(-(fP@r{2RtsM$>4^2JIzdCa|ibCzW$(N6BDle@oPHx9vZcix_ghVvI)GBlF z_KYpPECfjZ<`jJPC%Z_dJePkP2Hqenys6VC$eSa|$$gJnIHvHw2)v~@eY!LP)i*w=dV zc<0SjW3_{JH*R=ACA+M=RP`xWTY0XUJ@bg?R_-cASJV}Fr>W}*t@D%YR|)X-?28Fa z3uHzmOnFK|7_Ek!uemaL!j-~dyN3MqqscTgK$9_ur zSv2yrLPywj67A8D3dYT~=C=)oPO$dd-fQ!uDfpxa*}2gSn|JYGCL{=lK;l1tlI%|C z$Wpj=#_TD=xg-JU;9!510|rw*HyeLxq=ThkoPLX9?3y##Er03wb}P~(I*n0<=7p3{ zi3iT8F_cs0rMCq0t-iUryv-wm?`DcR*Pt}TMn0Q+RQ;A)%wOo2pFjM3_U=>cx{0+$ zCoYLMQ${Z8(|jCn7f0rW$NTsL~4ihX@Po0v=D!_0a{@ zNrT4idF{S(>K9&`?Ee_kkzt?a*$N}r8rJ||AB{HjuaVl=nIw6)i>+7m@W>H-> z6lM_&A#EUUNW`fpl(u%E9h40NKl^#Zl77t=k4-YsT!S0wL75&c6QdjEWZleqI;6gR zHAl~^#{}<7#?}j3{zCqYa&vUkYHoaXtZw3opO*O2;h3>&BsDr*Cg;xAH1c@)ie(LU z{a#HwcNBa#%|+rI1fm>jrRbarf0~a`j}n6!MZ_=7d&=qRrkGcGTyZ)es;8EZ$cm_8w*}j1bny(&Bx-$!h6(WuFqgL2j&d=x~{2;n=hTmcNR(8eQCT0wP5M* z;Kdn+u)!UhAm^HP_lGi9W*S|>*Q7Ofa(iU~*QJi&RvAm|@szb%#si9Amv#O+dE~Aj znc4(T(`wPdcFU&UMwgOz$5LoTyj$PL*j3`Clu5Nk8UCfPa06fL`*1|PzGDd&(wW=N z1ucDbG6F5N#JE6$+9!3n`0?zoIn6a<;RV_atWS7-Qtk|uV^hq#kqRNS7mm&f2MR{@ z#}%YfO<6sizu`0*dcs(@?L z(`+kEbl4R0n_AHO<4??;-_fD16#2*u)M>Uzu>ll@{E_6QUFC^ftk79&*AAR6QE71K zGMXgMjTZ7nSFXwL;N^qG*z{a@T`FyChtpe%DN+iI4dspl-@B6iqUaBWWje?tSaHLp zxydcj{BEJ_t{E(ND$mbovi0pY-?5{iDvxm2HmEWrNWy3{O4!r4GOMXAgEgR3COG;<% zX=<2U|0d}dQAkoBsa11$Qf88s#XlsZ;Aj3;J;Ml5a;dhVr1R~!!o+LDrY3wL*|r2E z@+|x0DTKjp*-JUs{iUs(%bpt!Z#H7pR)6wN^9J_NlhPwVgn~N6q7jQ1Yu18YA&W~` zH-oH#iI;gc|LsLA&J6s8V5j~jC zBJqeKIN1&V^2+Ij;Sko@UY&07#Fk(zxZ!^f5Hrtt2l)O-ahv%J|M08%rQv;b9?<_8 za?#Q!McnV~3_lBIg+H;@!8K#mNQr-#rrK^iwHlsn{UZ;%;o%?Mb~o&gJ^xeiPtx{( z(FT3-t7q8*G(|9VYH`E~pQ(0w@&{)6UokP`ysOpejKM~7W#bzfA#Em)$<@?loKKbY zws&)cqwZ)Abk`b^(F)c#5LYXMkhe&EYf z@Fk7~>)>IyUdbKRMSev>h{iC_`c&slW+>f0GOE&*$n2l!7i=ef*x>+5*~CE_A_F`7 z?nU0!Kg~pm%^s7(h>X;=rmN+z?PSCstIneX-yToC=!D6%&i1eW(vA$jE|5h&%*5CCv8_BH z?nZVdFX-nCIW>mZthc;7$t=c@%XXQ`PbTHfP;V}uhSK!L8?*VPKK8`#SBc;MlWQ^b zwRT>k^_Qj&r1NK<5r1L_)wdqoe~ea%=FZXQ3ETIy_(op9@1h(%FX{QE#bEhq&I4kj z$5$3ZBPhal)>j2dP2{_I$*=2hFIukT`8w0Ej=2; z95EE9Dc5|_fxXGUI(jny96D?HW@CS5E#n$EB6ANu5&LDoZDO4HY;!%`KTSWET+KBD zR2enzSL#oUteekAt`qk9MpHtevyAVWamuqZi@AO-(Zk zE!*v-BQacC271ou`o7vl>H8}6Hl`5*Uqrsui5Cajso<}vC^eC=A`V2satld zg-2-{K|Nudr?8UbpD*Yg5_#&nE26FX#`eMrp2oh#aP@YmIWJ#y!PnjbheZU7-Nb8w z@53KSRk)rjittRsSF(pv#f@i z)26-f@o?&Xep`z``00w@?4VJs9;>&tgFHbY{KgelG$5C2{kB_P8czSTcSYi-L`Ouc zIF_+X2qvAKk$frC#>(;OY~FqfeZ9TMTMK(s_JiNz>g;-M4Rm&GS;_9>{*O?~aJz#l zO>h_J6bWZ=v%Hq&xkf+>cAq|{RNIt zL*kx4(KzvRZ%GewbUpNE#6k#Wq)u zhslh17n29)jqPxqWjEv|zvnS(!ler`I$k{bxfbFd*lH(HvM_$v@7D5;#KVEt*ZDcH zPJwWz^6Lh^MBwK8T|al@%t+Fq*&YoNmi4RGCsvs(zb&q<(+=~t|5=S2J$HygJ@a4x z7BwCO7D%*O9C12-&R{?=05z)AQq?D#f^V<-+g$tj;MR>G_(QG^i^tiur+Nk1c@#PM z^QTk;+zQHf%%4INI+J@)%}K1?z(iwdpFDj9{#z|;w^f?5f97- z5qn?8a4qV%MN@fH6@`&};th>v|}D^jkP<=j9EtJKge2A8fpWR1~a6HZTnO zg0|fXW$csscj>HU!wTA3pxB7 zc$%%Y>ip~tPm_Gqt37Ub|SR~TeiFIM4(R{+Bw}iQx_@Z7RBG9a}-JjZFHz_Q%=?Q?s)%E26Zt*3?`oX z@`TG%cS?jUSf#SI)J?)agIkU#FWpi<5S>(&Y=icegfZKHE&BC>4O%mDVa;df0c4xI z#^w0;-V^&T62_^qjfdvg+Avo-&AU72SA8msN2fTt3Y;iDUK}XjJPdo@G_kR^9*%o5 zEM+i&G=F0wJykyI%SPZ1S?w=8#wQW#u_l2F!hO~$&q>oe3Ape*^- zFLY6VB+bZRwO&CYvlqq$u{^HB6?ya(Hi+m8Y5FnC85GwqhP+zTznU{Cghnrddwd+d zDZ|mF2QIv_oP|&)>x?#_FWr#{qFwA`*TMRZnkjT$-`y9Ow8U1{AXXNir5VCtL*1kD zU5toz{_%m`(aZLpzMH*2+|zV6lOt%G>gr0Qh;r&^TnzI@2ule{HThSeCh9lTrgCYd zw6T%J2CC&wgKzrabNVv@1WkyK>p%ak&e@9$0}gpu*G}z4_KZRXV43 zG->9CdQo02^!n7zN`3*%okoJmK=}cb4XqR`-BTHbQhg(*q3%L$MLEp&b8ccUtzsLc zDdmnGJSyr+(OyoDm~zZ~IMB2HjmFK(98;;f{lHPQvfN@N(~>s60Yh*%UKdx(I>cTG z785>@JsA+)BvZHzF-~N<1#cA`KCzPehNQ&?%m!jaYU&+cYd60iOnpe*L2$b7YBrO; zarx~j5LA<}eh6lsr(wz@PyojSTYr8=ETo?vM(wt10BUL5KrD>Tt%p2S&4 zz9%)-yqo+e^j85>P38ODm&Js!tCwbh#S6td%r4;<8)1)0Bf@G)Au>Jpjuw{0s($0# zDDf(37D8n;U$#ubQLhk5n#sRvW=2}JL9dg%;YT0kU^eb6&|BERjOMRKFEC{Fe63Rp z3el(bZ^nH}!#A>;8o|=Oj)vIX&8YOsHK zCtljUj6FO_F@TgTTwCTX*yojNV5lv(u-T^xj#Slu?0)hw!IdrXD9F^5XlZYM!nloL z)}e#QpInTs;8B9Zg*lABo8Q}a7dH2hfK_~jf{E{pv_Lu69?EI>tR0pnGD))Z!-%pZ z=h$hA`#NlvkYUq`gnf$Wh{y8J2c_FtS#H|?MlUAJ zJv=Y5)9HJSWCAV9OhR$0YZI2i-*U_8DC$Nb^cDsA{Xh{xYWvIXXE|f{vjQfnWMHH| zF6uf(4bJ9sC89KN`=(^UGKT>&>-KsZPWvR!G2Qq<@Ifb%j|(MFXg&e`IolSOu+CHy zHLAoBZ>ZzF^h4|Ec=^ISe+*Frcox{%9v3?p6D2V}Mi{+r;4YVWyqXJ6%c!!~l9sVK zHGHq*g?Th#Uiy>s%J=BB(;0*KHLNSy@R{Ysp3kaYwN*)V=k>}oit7PIm<>r23d4yu zvTV0jG(0w5mQZW4i0WzxWruVXINYdhL-h|1h?+$_uTgXYKMHlGiVN3AE?kxEI<_3aP}GoiHnE9&K`$Bk2*8+A}Hg z()iAxTx`n3H5ug?V7O_hwK>cS~{#xL^h6;M&`OdiQ z9MYBAMm5x``TZ?F)R=d5gP`R(1_(qU>4Ze?3R#T1;2{@_V=29$#86m+!%Lw(*z<^o zgrmM+;cQ9RW0r}q-3>6Qv;#p|AxmU1ne{Y`J zDcgtoF3t=4oipY%AX{9a&Nu^4eG`^E$mqQ;f5q#7X}ve1>Zzj?PuJm}x?2mZcFC zihpzIm$@E3Rhic9BC+MCP!3glg2_<4_c)fjV#AW6?9^rc);#Z2$@KUi0zm({4cMKWI3*>5vSS5mCXXLpTL*JFO z89hm>61}tAVKlHms7HLi=n=4y<}&?>R#WmS=V?=+#ed*q|GDD%-?oP4^3H!9#w4U9 zt==Ne{|xjWW+NzwNBs4_;fMA1$(-IrMgIVaZ@!xR6A1QS8vFjANALlTeTJPOFFo&~ z-+&1x`Ti7$<3HoDFimK>)Gnc{Ml3^>BoVEKByPl)?<2r=V+yt^L&20KTDR74M~4{$ z@NCQ6BH|wY4)R3K8FMAErGHHisw-x>`8($ymg+~MrTR6`iu;~K>jYBa)bmk9#E-8@ zMrp&gj}FiM^$)z-L5%pokYAUCfOr2j&LF0&4hfQc_^+QKfAw6jo8GdJ|6h^dlBR8c zQ$;?dTC?$=E(+0e95m6FJ>)^TsL@RR4c(?AEACFg5-{L14t<^NWU-)9-cGi5ORCj= z&y3bnfxmDsq6?V#FP06&CT&`7ro6psuyc*nxNE1Yu&)1uaPuEoOv^swZ6p`p{NBvq za@}e;y(Fln_8(^JhzwA5>roOTuI%g9PJ4fFY5DV|+if>5OA4&&bPNrxe+!oGq<)&S z<_a#2m1rw9Hf_rXu8%2XJ$~sZ50=`BRf&?`V@qSXIFgt`x{m4ik>OlshJL^K?9H&I z8ep9o-&2pW>iTx5j~_^5rd>fZYH0}!-UMogY}G{%uiJa|0>tSF->1ycTFDE}Lz9i3 zyvc8PnqQF3iLfM#_{QKiyYM`;n3jwDI?q%S>LsTQ0dIJuI}xM|3^#+TKOMNS?~m9JFnXd9?zrSiTKVQCPbwG!8%2oGF{}M_~=qUG|}u zN5=X_YWQOn)qpnd@r8}waQJ``x7M}b^wxd~Hp%C&+K-irJ5$u}I2;J&c>c=Kheit4 zSZoy?vJM{391I zA{AaoIKK8Wi~2=vT5X%YZ%#;8n9|;SA8T)FV%>5G4>U5?cuQc+Oqy#!zP|^U8{s_Y%2&2z|1>6->^T(qX#ZFJFS^VvP5*OooNdbFO_Zq%9QR6FKf zCH1vbz}MdzF@gJM1m~$u@EY>u?KPp1`-jSqCpk%RlU1Vz@yafH!|kC`9T;1;M9($6 z`B5qOrZVUfuxAeyT{;5Qz>XOC%4;X21IoXsvLj>&RQ9y>)p(82Uh4!ige>JQ%HZJv7PSXfANjvn`G6u)%6e8UdHWXyuRJSJhqwy0wb74&4 z5pWPRJmR_q=eFgCY~5TXso1h6jYz(CFn7!=dCTb%?2)HOYFh-UebB{>=0-F02GwSAZ(;b&bV1v zT>0@eNXhm3BgUzAX&qPY*b({H(dNiwkT71Sm^SX_^unD?K?6ilP>DD_rTepyeZWS3 z{)pUi3dt=S_<$=}c1H}@7BPHkWNgg&go#wGryBK6k#KH%Tk@0a$=JVi1++PbV2?y1 zl33?22gB)x1U{#}lY355H}7aMjh6{d50Y-Bq{*eRuGELg^E%GV?)*g-ZW;XQ`x5mD znq1@i0($#rI};y**?>3KL_(q~IJobmFfeU=ZfiS?A1e;GWIaRFQ1ldmEfk>4N44XO ztqXHCRYD+=)PGGaxojM#f0nU0=@@js@Yx9N){6=y;iBZL~-Af5*%OJGj~kCbK4yAm>EhDU@-!bc;0Y05I=K35QroRZP8i=(9C{g=6KP#8yxyix!)vCYh!4fM|gpe%fYa77f z4=m={+IvCm=~+>w%#J@MUo7EcgIHakdNb5{Z=6^vqIXI9G=_0IH7L6$=30F#Dq>2~ zuw)q)H{hMRM*4jAF~#3f;vnjWfiW;VZ3J*gD)gDJ677X&dB5fN+CD9o7w#`Qcd;eX z3(|}~#dmmTG)o_CasgB(Cnv!}$?0Ic-#pRv-*J6i_a<@w(FQGfuC!&a>pv2O)LiIe z-V{a^wsCvyh3kp*9UJSjVU1H)(`ZdE z0ZcNq?0pKv!$AH{mK8Qqtmh0%kf_P>mFDiZyr#c%Uxi6+a2aVo{75TfDp3OGPOhIO zxo8N*d8ge`FIg}@uh(etI5P33rEr+&KFfr#70GZug*@h=_^kB(>O<)OIUXf283i{t!v>8GsF zm6er$XuJGCufDwkd+rDKI_NPh%FUH=W+Qge9wMClHfVR(J}r^gdAwJhHY6OLfFjl__k=Wv#0+}8o$cRSt*xwNoi7u6>HBNz-*u?{&p6Sz zvTK2b1qCd_IG;r>=+1XhUyLy=!EGVB8_-6Jg4*@lW%(M#iE1J;FkahnaFK93RX=Qb zjk#n;8Ua}B;tYKN!FZdxIu}^UO2C%#C8b!W(q=W9X>U);Wy{BNS-TA71@f>$I=9k} zyzi5fJB^x^O?a#oLU-27`gICNlk6Mam+x{bcpfI?7!Sm^%t&wEyb*{N6aW-3Gq|Hm z*I%0nB8zl&^>iZbl(sfDY_09l6^_W2c1cLhOaNuFqIvq#=TfdgE>d;_Zc7z+ZOeoy zS_>N?=k@*VTGz>YPAB_hIn&w|1GCRU%-%nJ#3r zcRy;#xbxV9FyM;+B^)J-#o2S8g z(D>{xi4=28mRby?+k4HW^^!Wisq)0nq#kY6ZFNRG3?bz(C^ZR6XN2Agl}rP)i!fe; za*J(IpB35^RG(NCppEP3_rVU$CnxNm6%-HvTudpyP!^9T-I4m#)YM(5r!5i6+S)as z2(|?`1;O+3{v`reGb0dPTAb)0A3Zc_%ENlhCOIZ%Z(_^47*II+xb5+1+E>47IXC6? zxv9x_Z0#e;ch`^@*y9bQJQw#NHI)Xkz3T~$fafDY?XrfVFB4W8#qR3^9e~y0GuC+x z$v_K8)rOWZcge^A7x^Bbh3d*N>-`G2Zhs%0w6rf)D^yg_9vn3_c_2ofmu;`|@NC}* zik+OCB;d?NfdlAP*drg88a=`RE#V8JY(UEs=kDRPqNbwkY)SQDx2Jr%y=e#Q<_J;@ zb_2~jVrOMYH^McXm zGUTXn3&`9PyN>T`!mkC#u*lP^KBY3K zd1DF)zw7!>GH6d24F&Ei0*zN5k8oY>O!t;ZT0u`)GcM;j&Z&xIK~Sh(n3#a z^@P2>y-O@u@l{bTR8(#S!N$?(e&=fR++@8w=5yaR!>9)X659NqzKR`5w?#@!7;pi-TL)kaUY}~&9308Jv?BUIdxKC z`^!}xAowIqHfo*y#*xKMiZI>={+D4@l`c{6f;{c4yseXdJ)W=m8ik}0<9t?9OxYP3 z4ULT;IiNtVg0seZqMx#`M|$^e5CCJKgM)*dvKq8j6Nf|| zfK3tH90B77CGV)wD`H4<6NiZavH`4<=T5E^tv76_oYKBNx3<<}cMa!M2Zomec=`5f zJ);Qhi@gb$5W-xKu28woy z=>s;r$l+nPL*b)~jhwYjMMWJWQFFZs!k#YauQ!uxw6(Q`i&9UVoioT(kJ>4rDyphc zzpBj;6p-9P?>R4#$+|%sOZq68NrW*2rDAaB?#d8gq!Px5sA(udBAV=u*M5%tfSCX} zH8eDkhGZ2uJ@XQNecTEj9&-t0{C0@pN95zkmc2j&kCnF3Q4NUuwm(^v=$qpnO?4gy zh9#i@l2WUYyyWEM=a)!Q1)QzMZqyvOL*J~AmCUyTA|$x9kI$q#jusOK?$>AbZrK#o zYC@RZp>0|~>n7>3>^hXKz&GWEsX*!huCo4i-B>n5JEf|o{n<1ad5eCAN3$3b0a)Wm60NDNdUSO~ytf*-%n{v)o{iP@TM@^{@9(K|r4KB}B6K4? z%}h*wxu7??*Z^w-y2_psTWS_^pqh3VozB=1P8%*dg&rH*U(8kj%F8dpUa_I;gtPDQ z90ojRVM_Gj>EFJ+fa&N4C^;o&S5#C0evG?!$wrV!hH*C19s+KEPLr)6ue^LWOmNF< zmapmF${|Bo+SAv^u;ZnrR8QW6VRYT3&)goL_za+fxYTdFe}8+V&CqM5EkfWa)eXoB z&MlJisNn<}8I$5YdD4OD0b1^778B#?H#>*c#S`ERu|^gufc&u0VzLM%ZzFHHqwPkv zuE>7#T^5aFB!>?q2-a(c*)e5-W%63m6-HDG12!F34*lA4GiL-X{`Vz_c7s_(I%A#d zmZHx&ZP?(@5Md_OSK1C$X{-B&x6kF2>o7RoM1U%ag_-Jxl%f5U@9};|&<#=ib~s?E zw8wwHw1`L(wZ|Vke9RF^?1l?szjp4rqy5XH}R7~?1d?{S&+#w(9Z3^h3Sj7A(j<>Z8!`~-YA zWZ!^xG3o0IE~!?4qxN(K-Da4zwo_Nv9HA}h@8^i;a8-Z_dNh(|7y#TidMK z+S*8xLQ?CUGL*BkGXX#C*QHC8t-^Ss+(O4Yj%z(viOw+Mxv|U3Hhz&s5bcOOJ>BBsZ z4*%6k$=ll6a#QG6SjCJO>DSrmKe~ld6Lce+{h*M0D|MI=zgK-SxGdiM zbJSK>!ej+hD~M%}DRJ?kkm07-*j(A%T=Bsm_*}Pq&}LQMv$xa8a~&VuIyl+#C9LKb z!#jHu$|P4uM<k z)l`y&vDG7vwzMtECNNFA&?rc209hjv8fb_a1(!x3D99p0SY%(~4lNVtsA$+#6f`YM ziy8KWMS}<=vM5Uk0R(bL*ut*a=052eJu~yC&*?w&D<|*0dR6saRoz?P{qFnV(_9n% z?D!pMG!e$sWeUTEpBifUhyj1bee(lDUAkcD4m(1WTJWvAFjlBv-(V%NO0dxKNM@=HpWE} zrR|i{ZKWF44srZ~!5m8#n=Nm`5i|Pk`}fA<#je}-N@ALOhY<>u^pc<@$68wt zO5V>hq}fx?0GTj0?rm?^)7z!F!EXOn5X#ne9KVmc!D6u*iPEK-+=|64`pxZ*x0#oP z90EhiqQ~=5#)B)h%WW|-3IEPD)9_|}Fg#COYd*YT(7%?~=PtDpWbZS15(%GC5))2J zYDwQ`*YOPe5*_-b0-ir{|6xnCG5`F6&+3#YAL#V;v35 zSyPK>nGm1+QWG|CEfHFv)p_0vCd(kGjvXuZ0D;)oJf6HSjBPdDy5>^d-K4(wMY|#A zyI@ek{<*=-9}1H;m(k~6649X`j18{*Nb?18)^Wl30NW6OAF{y(u#RhSQsdS=gwzO z2?Q}qd?kWJ*t5Dj&I2FUm_IpLB$@-zy|x4)4q()}Z!LX0#%S7^cF{f2GHNQS)9TyX zM)gZ%mt)Gw_6<$9Lp+H09|-8>l<_ez!G7j91qX|~0Z!PE*l^Bx@%&vX(c5u zrxr={qBofIz2QIZSBCj8ceT8{&XWSu33Sn4vxizD^scvaJ1ig7l$02nn}Zl{UFzM` zPGnKBA(MD2Pk+;Ld)yN>Sbr7p1shHcv^G;0P98u<@l;H6A*LIL&tsbu*Hyf{uZOmIx zb7&7_8f`N-x52p8t1_+R4c9)H(1AvdqHuK0OPhC!gpYpV12_h7ncn z7@a};Ar`oj; z+K)H3sWpa-=}VLvf~z9Rgzfua#6_EmGlnCou734&w|fkdkg(Xnym{VT`=D-f+}Jnp zzYnK@JJJ=Xc+botng*nffe`<94uVnI6mQ0YGganD0P8Yvs5gp=XUMO7#VQb-n7EA) z(`zlCiCnN95nLmO`Y{*H4Kqktk>5KtNmPPACqQv9w1`d=SMms5!WNa6P*G~VoWvJN zX3tdVvkPo;eDMa25BYF>dPQ%PF}A}PTg6Soaj=J(*+1cCn+0YsQfje7plF`?f++-Ag*Y9Lu+z6u86cs!NK+rrs$yOrGEk` CH}}2( From d5bd1a17eb85846cfc13ef22729523ee0670f101 Mon Sep 17 00:00:00 2001 From: DMO Date: Thu, 16 Jun 2022 16:36:17 +0900 Subject: [PATCH 186/258] Setting defaults of CreateMultiverseUsd to reasonable values. Creating a schema entry for multiverse creators and setting defaults. --- .../plugins/create/create_multiverse_usd.py | 6 ++-- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/maya.json | 16 +++++++++- .../schemas/schema_maya_create.json | 31 +++++++------------ 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index adf0acc7c2..5290d5143f 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -37,15 +37,15 @@ class CreateMultiverseUsd(plugin.Creator): self.data["writeUVs"] = True self.data["writeColorSets"] = False self.data["writeTangents"] = False - self.data["writeRefPositions"] = False + self.data["writeRefPositions"] = True self.data["writeBlendShapes"] = False - self.data["writeDisplayColor"] = False + self.data["writeDisplayColor"] = True self.data["writeSkinWeights"] = False self.data["writeMaterialAssignment"] = False self.data["writeHardwareShader"] = False self.data["writeShadingNetworks"] = False self.data["writeTransformMatrix"] = True - self.data["writeUsdAttributes"] = False + self.data["writeUsdAttributes"] = True self.data["writeInstancesAsReferences"] = False self.data["timeVaryingTopology"] = False self.data["customMaterialNamespace"] = '' diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index f0b2a7e555..5c5a14bf21 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index df54e44c56..7e404188cf 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -71,7 +71,21 @@ }, "CreateMultiverseUsd": { "enabled": true, - "strip_namespaces": true + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdComp": { + "enabled": true, + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdOver": { + "enabled": true, + "defaults": [ + "Main" + ] }, "CreateAnimation": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 4dd54c81a0..09287a8b50 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -143,29 +143,22 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "CreateMultiverseUsd", - "label": "Create Multiverse USD", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "strip_namespaces", - "label": "Strip Namespaces" - } - ] - }, { "type": "schema_template", "name": "template_create_plugin", "template_data": [ + { + "key": "CreateMultiverseUsd", + "label": "Create Multiverse USD" + }, + { + "key": "CreateMultiverseUsdComp", + "label": "Create Multiverse USD Composition" + }, + { + "key": "CreateMultiverseUsdOver", + "label": "Create Multiverse USD Override" + }, { "key": "CreateAnimation", "label": "Create Animation" From eb3e0d626565697ff3fa7029cca9a4e318a61a8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:35:57 +0200 Subject: [PATCH 187/258] add new representation only if was created --- openpype/plugins/publish/extract_jpeg_exr.py | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 3017656621..de01ebd2de 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -92,17 +92,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug( + "Adding thumbnail representation: {}".format(new_repre) + ) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] From 5bdbebf4cc163d182d599561967088382ba0adc7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:36:03 +0200 Subject: [PATCH 188/258] create only one thumbnail --- openpype/plugins/publish/extract_jpeg_exr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index de01ebd2de..a467728e77 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -105,6 +105,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "Adding thumbnail representation: {}".format(new_repre) ) instance.data["representations"].append(new_repre) + # There is no need to create more then one thumbnail + break def _get_filtered_repres(self, instance): filtered_repres = [] From b722062928b081f9d7a7e51ec37d7118834459c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 16 Jun 2022 12:35:06 +0200 Subject: [PATCH 189/258] Fix: Kitsu module first synchronization Add a `None` to `pop(...)` to avoid process abortion at early stage of production --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 673a195747..08e50d959b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -85,7 +85,7 @@ def update_op_assets( # Frame in, fallback on 0 frame_in = int(item_data.get("frame_in") or 0) item_data["frameStart"] = frame_in - item_data.pop("frame_in") + item_data.pop("frame_in", None) # Frame out, fallback on frame_in + duration frames_duration = int(item.get("nb_frames") or 1) frame_out = ( @@ -94,7 +94,7 @@ def update_op_assets( else frame_in + frames_duration ) item_data["frameEnd"] = int(frame_out) - item_data.pop("frame_out") + item_data.pop("frame_out", None) # Fps, fallback to project's value when entity fps is deleted if not item_data.get("fps") and item_doc["data"].get("fps"): item_data["fps"] = project_doc["data"]["fps"] From edf13e877f7416c2a64e1a123003bdfffce10d64 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:45:06 +0200 Subject: [PATCH 190/258] removed requirement of pypeclub role in default settings --- .../defaults/system_settings/modules.json | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..8cd4114cb0 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -59,13 +59,11 @@ "applications": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] } }, @@ -73,25 +71,21 @@ "tools_env": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "avalon_mongo_id": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "fps": { From 8857423828377a319fd80292f9b0ca0e49e5b2ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:49:52 +0200 Subject: [PATCH 191/258] Changed label of custom attribute action --- .../ftrack/event_handlers_user/action_create_cust_attrs.py | 4 ++-- website/docs/module_ftrack.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 88dc8213bd..d04440a564 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -140,9 +140,9 @@ class CustomAttributes(BaseAction): identifier = 'create.update.attributes' #: Action label. label = "OpenPype Admin" - variant = '- Create/Update Avalon Attributes' + variant = '- Create/Update Custom Attributes' #: Action description. - description = 'Creates Avalon/Mongo ID for double check' + description = 'Creates required custom attributes in ftrack' icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "create_update_attributes" diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index fd9687ed9d..667782754f 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -26,7 +26,7 @@ You can only use our Ftrack Actions and publish to Ftrack if each artist is logg ### Custom Attributes After successfully connecting OpenPype with you Ftrack, you can right click on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. -To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. +To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Custom Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. From bcb52309517350de2ad0aa91f28ab317dfffdbce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 15:05:46 +0200 Subject: [PATCH 192/258] Fix - add default target for New Publisher --- openpype/pipeline/create/context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2f1922c103..7931ea400a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -829,9 +829,10 @@ class CreateContext: discover_result = publish_plugins_discover() publish_plugins = discover_result.plugins - targets = pyblish.logic.registered_targets() or ["default"] + targets = set(pyblish.logic.registered_targets()) + targets.add("default") plugins_by_targets = pyblish.logic.plugins_by_targets( - publish_plugins, targets + publish_plugins, list(targets) ) # Collect plugins that can have attribute definitions for plugin in publish_plugins: From 5b9d2f2915f59e4b98f00bdf5855a7f093a7c46a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 16:41:09 +0200 Subject: [PATCH 193/258] Fix - add AE validation scene on render.local It was missing for local rendering. --- .../aftereffects/plugins/publish/validate_scene_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 14e224fdc2..6fe63fc41e 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -54,7 +54,7 @@ class ValidateSceneSettings(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder label = "Validate Scene Settings" - families = ["render.farm", "render"] + families = ["render.farm", "render.local", "render"] hosts = ["aftereffects"] optional = True From ab0bc2108c5d9a954e7fb9adde3cda732ace9afe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jun 2022 16:44:18 +0200 Subject: [PATCH 194/258] deadline: fixing misidentification of revieables --- .../deadline/plugins/publish/submit_publish_job.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..d2f709825c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -642,9 +642,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") - # test also instance data review attribute - preview = preview or instance.get("review") - # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -726,6 +723,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families = ["render"] + # pass review to families if marked as review + if data.get("review"): + families.append("review") + instance_skeleton_data = { "family": "render", "subset": subset, @@ -754,10 +755,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) - # also include review attribute if available - if "review" in data: - instance_skeleton_data["review"] = data["review"] - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 2890c950f5b58fd7654497aae22016d2d1df56ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:46:31 +0200 Subject: [PATCH 195/258] trigger open events --- openpype/tools/workfiles/files_widget.py | 45 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 68fe8301c9..709f05b26b 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -90,7 +90,9 @@ class FilesWidget(QtWidgets.QWidget): self._task_type = None # Pype's anatomy object for current project - self.anatomy = Anatomy(legacy_io.Session["AVALON_PROJECT"]) + project_name = legacy_io.Session["AVALON_PROJECT"] + self.anatomy = Anatomy(project_name) + self.project_name = project_name # Template key used to get work template from anatomy templates self.template_key = "work" @@ -98,6 +100,7 @@ class FilesWidget(QtWidgets.QWidget): self._workfiles_root = None self._workdir_path = None self.host = registered_host() + self.host_name = os.environ["AVALON_APP"] # Whether to automatically select the latest modified # file on a refresh of the files model. @@ -385,8 +388,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - project_name = legacy_io.active_project() - self._asset_doc = get_asset_by_id(project_name, self._asset_id) + self._asset_doc = get_asset_by_id( + self.project_name, self._asset_id + ) return self._asset_doc @@ -396,8 +400,8 @@ class FilesWidget(QtWidgets.QWidget): session = legacy_io.Session.copy() self.template_key = get_workfile_template_key( self._task_type, - session["AVALON_APP"], - project_name=session["AVALON_PROJECT"] + self.host_name, + project_name=self.project_name ) changes = compute_session_changes( session, @@ -453,8 +457,35 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_name = asset_doc["name"] + + emit_event( + "workfile.open.before", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self._enter_session() host.open_file(filepath) + emit_event( + "workfile.open.after", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self.file_opened.emit() def save_changes_prompt(self): @@ -602,10 +633,10 @@ class FilesWidget(QtWidgets.QWidget): # Create extra folders create_workdir_extra_folders( self._workdir_path, - legacy_io.Session["AVALON_APP"], + self.host_name, self._task_type, self._task_name, - legacy_io.Session["AVALON_PROJECT"] + self.project_name ) # Trigger after save events emit_event( From ee4b635edd51523e46864e00c5ec873e477e3e3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:51:55 +0200 Subject: [PATCH 196/258] trigger open workfile events in workfiles tool --- openpype/tools/workfiles/files_widget.py | 54 +++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 709f05b26b..a7e54471dc 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -1,6 +1,7 @@ import os import logging import shutil +import copy import Qt from Qt import QtWidgets, QtCore @@ -434,6 +435,21 @@ class FilesWidget(QtWidgets.QWidget): template_key=self.template_key ) + def _get_event_context_data(self): + asset_id = None + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_id = asset_doc["_id"] + asset_name = asset_doc["name"] + return { + "project_name": self.project_name, + "asset_id": asset_id, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + } + def open_file(self, filepath): host = self.host if host.has_unsaved_changes(): @@ -457,33 +473,19 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) - asset_name = None - asset_doc = self._get_asset_doc() - if asset_doc: - asset_name = asset_doc["name"] - + event_data_before = self._get_event_context_data() + event_data_before["filepath"] = filepath + event_data_after = copy.deepcopy(event_data_before) emit_event( "workfile.open.before", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_before, source="workfiles.tool" ) self._enter_session() host.open_file(filepath) emit_event( "workfile.open.after", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_after, source="workfiles.tool" ) self.file_opened.emit() @@ -598,9 +600,14 @@ class FilesWidget(QtWidgets.QWidget): src_path = self._get_selected_filepath() # Trigger before save event + event_data_before = self._get_event_context_data() + event_data_before.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) emit_event( "workfile.save.before", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_before, source="workfiles.tool" ) @@ -638,10 +645,15 @@ class FilesWidget(QtWidgets.QWidget): self._task_name, self.project_name ) + event_data_after = self._get_event_context_data() + event_data_after.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) # Trigger after save events emit_event( "workfile.save.after", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_after, source="workfiles.tool" ) From 94f9d6309822431dc00218687207cefcc4397b11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:26 +0200 Subject: [PATCH 197/258] modules have method that can be called on host installation --- openpype/modules/base.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index bca64b19f8..b9ccec13cc 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -463,6 +463,25 @@ class OpenPypeModule: pass + def on_host_install(self, host, host_name, project_name): + """Host was installed which gives option to handle in-host logic. + + It is a good option to register in-host event callbacks which are + specific for the module. The module is kept in memory for rest of + the process. + + Arguments may change in future. E.g. 'host_name' should be possible + to receive from 'host' object. + + Args: + host (ModuleType): Access to installed/registered host object. + host_name (str): Name of host. + project_name (str): Project name which is main part of host + context. + """ + + pass + def cli(self, module_click_group): """Add commands to click group. From 0c28a29650491603a04ad9f9178e7cdc81c9f73b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:48 +0200 Subject: [PATCH 198/258] trigger on_host_install method on host installation --- openpype/pipeline/context_tools.py | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index c6e09cfba1..8643c3d69d 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -33,6 +33,9 @@ from . import ( _is_installed = False _registered_root = {"_": ""} _registered_host = {"_": None} +# Keep modules manager (and it's modules) in memory +# - that gives option to register modules' callbacks +_modules_manager = None log = logging.getLogger(__name__) @@ -44,6 +47,23 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +def _get_modules_manager(): + """Get or create modules manager for host installation. + + This is not meant for public usage. Reason is to keep modules + in memory of process to be able trigger their event callbacks if they + need any. + + Returns: + ModulesManager: Manager wrapping discovered modules. + """ + + global _modules_manager + if _modules_manager is None: + _modules_manager = ModulesManager() + return _modules_manager + + def register_root(path): """Register currently active root""" log.info("Registering root: %s" % path) @@ -70,10 +90,12 @@ def install_host(host): avalon host-interface. """ global _is_installed + global _modules_manager _is_installed = True legacy_io.install() + modules_manager = _get_modules_manager() missing = list() for key in ("AVALON_PROJECT", "AVALON_ASSET"): @@ -112,7 +134,14 @@ def install_host(host): else: pyblish.api.register_target("local") - install_openpype_plugins() + project_name = os.environ.get("AVALON_PROJECT") + host_name = os.environ.get("AVALON_APP") + + # Give option to handle host installation + for module in modules_manager.get_enabled_modules(): + module.on_host_install(host, host_name, project_name) + + install_openpype_plugins(project_name, host_name) def install_openpype_plugins(project_name=None, host_name=None): @@ -124,7 +153,7 @@ def install_openpype_plugins(project_name=None, host_name=None): pyblish.api.register_discovery_filter(filter_pyblish_plugins) register_loader_plugin_path(LOAD_PATH) - modules_manager = ModulesManager() + modules_manager = _get_modules_manager() publish_plugin_dirs = modules_manager.collect_plugin_paths()["publish"] for path in publish_plugin_dirs: pyblish.api.register_plugin_path(path) From 89a7d438b4b37e0903ae33f54de58bb04a03b9c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:42:46 +0200 Subject: [PATCH 199/258] added human readable keys into taskChanged topic data --- openpype/lib/avalon_context.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..243e89e00d 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -797,8 +797,14 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): else: os.environ[key] = value + data = changes.copy() + # Convert env keys to human readable keys + data["project_name"] = legacy_io.Session["AVALON_PROJECT"] + data["asset_name"] = legacy_io.Session["AVALON_ASSET"] + data["task_name"] = legacy_io.Session["AVALON_TASK"] + # Emit session change - emit_event("taskChanged", changes.copy()) + emit_event("taskChanged", data) return changes From cbb876c155fb6f31356d74404f7763689e09a6d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:43:18 +0200 Subject: [PATCH 200/258] moved timer change on task change to timers manager module --- .../modules/timers_manager/timers_manager.py | 18 ++++++++++++++++++ openpype/pipeline/context_tools.py | 8 -------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 3f77a2b7dc..3cf1614316 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -7,6 +7,7 @@ from openpype_interfaces import ( ITrayService, ILaunchHookPaths ) +from openpype.lib.events import register_event_callback from openpype.pipeline import AvalonMongoDB from .exceptions import InvalidContextError @@ -422,3 +423,20 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): } return requests.post(rest_api_url, json=data) + + def on_host_install(self, host, host_name, project_name): + self.log.debug("Installing task changed callback") + register_event_callback("taskChanged", self._on_host_task_change) + + def _on_host_task_change(self, event): + project_name = event["project_name"] + asset_name = event["asset_name"] + task_name = event["task_name"] + self.log.debug(( + "Sending message that timer should change to" + " Project: {} Asset: {} Task: {}" + ).format(project_name, asset_name, task_name)) + + self.start_timer_with_webserver( + project_name, asset_name, task_name, self.log + ) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 8643c3d69d..3e63eeba27 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -16,9 +16,7 @@ from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings from openpype.lib import ( Anatomy, - register_event_callback, filter_pyblish_plugins, - change_timer_to_current_context, ) from . import ( @@ -117,8 +115,6 @@ def install_host(host): register_host(host) - register_event_callback("taskChanged", _on_task_change) - def modified_emit(obj, record): """Method replacing `emit` in Pyblish's MessageHandler.""" record.msg = record.getMessage() @@ -197,10 +193,6 @@ def install_openpype_plugins(project_name=None, host_name=None): register_inventory_action(path) -def _on_task_change(): - change_timer_to_current_context() - - def uninstall_host(): """Undo all of what `install()` did""" host = registered_host() From 3da1720702a2f21350e8a9cb4f17bbca4e11db23 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 18:04:08 +0200 Subject: [PATCH 201/258] Fix - validate_scene_settings was failing for legacy instances Fix - renderLocal is published to 'render' folder same as render farm --- .../hosts/aftereffects/plugins/publish/collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index fa23bf92b0..97b3175c57 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -21,7 +21,7 @@ class AERenderInstance(RenderInstance): projectEntity = attr.ib(default=None) stagingDir = attr.ib(default=None) app_version = attr.ib(default=None) - publish_attributes = attr.ib(default=None) + publish_attributes = attr.ib(default={}) file_name = attr.ib(default=None) @@ -90,7 +90,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): subset_name = inst.data["subset"] instance = AERenderInstance( - family=family, + family="render", families=inst.data.get("families", []), version=version, time="", @@ -116,7 +116,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): toBeRenderedOn='deadline', fps=fps, app_version=app_version, - publish_attributes=inst.data.get("publish_attributes"), + publish_attributes=inst.data.get("publish_attributes", {}), file_name=render_q.file_name ) From e9168118d73cb265a4f5597b5d577197b41db4e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:25:36 +0200 Subject: [PATCH 202/258] handle prerender earlier prerender --- .../deadline/plugins/publish/submit_publish_job.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index ae4ada709a..6d08e72839 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -722,14 +722,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues." ).format(source)) - families = ["render"] + family = "render" + if "prerender" in instance.data["families"]: + family = "prerender" + families = [family] # pass review to families if marked as review if data.get("review"): families.append("review") instance_skeleton_data = { - "family": "render", + "family": family, "subset": subset, "families": families, "asset": asset, @@ -751,11 +754,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "useSequenceForReview": data.get("useSequenceForReview", True) } - if "prerender" in instance.data["families"]: - instance_skeleton_data.update({ - "family": "prerender", - "families": []}) - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From a1676d5c44de04d880996afc738d245bcd65e66d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:58:26 +0200 Subject: [PATCH 203/258] create representation variable all the time --- .../hosts/nuke/plugins/publish/precollect_writes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index e050fc8c52..7e50679ed5 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -72,12 +72,12 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if "representations" not in instance.data: instance.data["representations"] = list() - representation = { - 'name': ext, - 'ext': ext, - "stagingDir": output_dir, - "tags": list() - } + representation = { + 'name': ext, + 'ext': ext, + "stagingDir": output_dir, + "tags": list() + } try: collected_frames = [f for f in os.listdir(output_dir) From 7c7ae5d3dccefb409870b91ad8ef50097268c815 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:38:14 +0200 Subject: [PATCH 204/258] Fix Maya 2019 support `__iter__` is only implemented in Maya 2020+ for Maya Python 2.0 iterators. --- openpype/hosts/maya/plugins/publish/collect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 433fa9886d..6cc9b9d7e4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,18 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - next(iterator) # ignore self + iterator.next() # ignore self while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - next(iterator) + iterator.next() continue traversed.add(path) - next(iterator) + iterator.next() return list(traversed) From 5b1ab9e7d3694f127eb3457f5e8e2dab767008dd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:52:35 +0200 Subject: [PATCH 205/258] Shush hound --- openpype/hosts/maya/plugins/publish/collect_instances.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 6cc9b9d7e4..ad1f794680 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,19 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - iterator.next() # ignore self + # ignore self + iterator.next() # noqa: B305 while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - iterator.next() + iterator.next() # noqa: B305 continue traversed.add(path) - iterator.next() + iterator.next() # noqa: B305 return list(traversed) From b9abbcd61629d5103413399b5048ca5ad2d23545 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 12:11:00 +0200 Subject: [PATCH 206/258] fixed extract thumbnail in nuke --- .../nuke/plugins/publish/extract_thumbnail.py | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 092fc07d6c..a622271855 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -65,48 +65,46 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes = [] # try to connect already rendered images - if self.use_rendered: - collection = instance.data.get("collection", None) - self.log.debug("__ collection: `{}`".format(collection)) + previous_node = node + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - thumb_fname = list(collection)[mid_frame] - else: - fname = thumb_fname = os.path.basename( - instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." + thumb_fname = list(collection)[mid_frame] + else: + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." - self.log.debug("__ fhead: `{}`".format(fhead)) + self.log.debug("__ fhead: `{}`".format(fhead)) - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join( - staging_dir, thumb_fname).replace("\\", "/") - self.log.debug("__ path_render: `{}`".format(path_render)) + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) + if self.use_rendered and os.path.isfile(path_render): # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + rnode = nuke.createNode("Read") - rnode["file"].setValue(path_render) + rnode["file"].setValue(path_render) - # turn it raw if none of baking is ON - if all([ - not self.bake_viewer_input_process, - not self.bake_viewer_process - ]): - rnode["raw"].setValue(True) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + temporary_nodes.append(rnode) + previous_node = rnode # bake viewer input look node into thumbnail image if self.bake_viewer_input_process: From d230cb7d49170d71eeaf8b3f201b3e966817132b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 12:39:44 +0200 Subject: [PATCH 207/258] Fix - audio validator for Harmony has wrong logic Wrong condition stayed after change from asset to raise (it should be negated). --- openpype/hosts/harmony/plugins/publish/validate_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/harmony/plugins/publish/validate_audio.py b/openpype/hosts/harmony/plugins/publish/validate_audio.py index cb6b2307cd..e9b8609803 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_audio.py +++ b/openpype/hosts/harmony/plugins/publish/validate_audio.py @@ -47,6 +47,6 @@ class ValidateAudio(pyblish.api.InstancePlugin): formatting_data = { "audio_url": audio_path } - if os.path.isfile(audio_path): + if not os.path.isfile(audio_path): raise PublishXmlValidationError(self, msg, formatting_data=formatting_data) From 29def2f2679ca95fa645ef6afe16a1b48bf1b9ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 13:17:05 +0200 Subject: [PATCH 208/258] fix kwarg key --- .../hosts/webpublisher/webserver_service/webpublish_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index b1041bf6cb..457bcd7f93 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -107,7 +107,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = get_assets(project_name, field=query_projection.keys()) + asset_docs = get_assets(project_name, fields=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 434e6bf78a8595eeb7c444d72711e547970b1d17 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 13:51:41 +0200 Subject: [PATCH 209/258] Fix - added json support to all resources encode method was missing for WebpublishRestApiResource --- .../webserver_service/webpublish_routes.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 457bcd7f93..4cb3cee8e1 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -38,18 +38,11 @@ class WebpublishApiEndpoint(ResourceRestApiEndpoint): return self.resource.dbcon -class RestApiResource: - """Resource carrying needed info and Avalon DB connection for publish.""" - def __init__(self, server_manager, executable, upload_dir, - studio_task_queue=None): - self.server_manager = server_manager - self.upload_dir = upload_dir - self.executable = executable - - if studio_task_queue is None: - studio_task_queue = collections.deque().dequeu - self.studio_task_queue = studio_task_queue +class JsonApiResource: + """Resource for json manipulation. + All resources handling sending output to REST should inherit from + """ @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -69,7 +62,20 @@ class RestApiResource: ).encode("utf-8") -class WebpublishRestApiResource: +class RestApiResource(JsonApiResource): + """Resource carrying needed info and Avalon DB connection for publish.""" + def __init__(self, server_manager, executable, upload_dir, + studio_task_queue=None): + self.server_manager = server_manager + self.upload_dir = upload_dir + self.executable = executable + + if studio_task_queue is None: + studio_task_queue = collections.deque().dequeu + self.studio_task_queue = studio_task_queue + + +class WebpublishRestApiResource(JsonApiResource): """Resource carrying OP DB connection for storing batch info into DB.""" def __init__(self): From a28f76374e583e6bf5d2f8490a39945a63da418f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:33:26 +0200 Subject: [PATCH 210/258] use get_openpype_username to fill data for templates filling --- openpype/lib/avalon_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..c27c5f572e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,7 +7,6 @@ import platform import logging import collections import functools -import getpass from bson.objectid import ObjectId @@ -19,6 +18,7 @@ from .anatomy import Anatomy from .profiles_filtering import filter_profiles from .events import emit_event from .path_templates import StringTemplate +from .local_settings import get_openpype_username legacy_io = None @@ -550,7 +550,7 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): "asset": asset_doc["name"], "parent": parent_name, "app": host_name, - "user": getpass.getuser(), + "user": get_openpype_username(), "hierarchy": hierarchy, } From cd90fb603ea3815445b9e181374428c2a5c4a18d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:37:38 +0200 Subject: [PATCH 211/258] remove unused imports --- openpype/plugins/publish/collect_current_pype_user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/plugins/publish/collect_current_pype_user.py b/openpype/plugins/publish/collect_current_pype_user.py index 1a52a59012..2d507ba292 100644 --- a/openpype/plugins/publish/collect_current_pype_user.py +++ b/openpype/plugins/publish/collect_current_pype_user.py @@ -1,5 +1,3 @@ -import os -import getpass import pyblish.api from openpype.lib import get_openpype_username From 1f42006f571343cf832b078d453e6804d68a3cbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:38:03 +0200 Subject: [PATCH 212/258] added user key to available template keys --- website/docs/admin_settings_project_anatomy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 6e0b49f152..106faeb806 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -68,6 +68,7 @@ We have a few required anatomy templates for OpenPype to work properly, however | `representation` | Representation name | | `frame` | Frame number for sequence files. | | `app` | Application Name | +| `user` | User's login name (can be overridden in local settings) | | `output` | | | `comment` | | From 34a79bfec9bd448986957885eaedfeae9424eab0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 15:31:51 +0200 Subject: [PATCH 213/258] fix showing after plugin hide and don't ignore close events --- openpype/tools/pyblish_pype/window.py | 89 ++++++++++----------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index d27ec34345..78590259bc 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -468,10 +468,8 @@ class Window(QtWidgets.QDialog): current_page == "terminal" ) - self.state = { - "is_closing": False, - "current_page": current_page - } + self._current_page = current_page + self._hidden_for_plugin_process = False self.tabs[current_page].setChecked(True) @@ -590,14 +588,14 @@ class Window(QtWidgets.QDialog): target_page = page if direction is None: direction = -1 - elif name == self.state["current_page"]: + elif name == self._current_page: previous_page = page if direction is None: direction = 1 else: page.setVisible(False) - self.state["current_page"] = target + self._current_page = target self.slide_page(previous_page, target_page, direction) def slide_page(self, previous_page, target_page, direction): @@ -684,7 +682,7 @@ class Window(QtWidgets.QDialog): comment_visible=None, terminal_filters_visibile=None ): - target = self.state["current_page"] + target = self._current_page comment_visibility = ( not self.perspective_widget.isVisible() and not target == "terminal" @@ -845,7 +843,7 @@ class Window(QtWidgets.QDialog): def apply_log_suspend_value(self, value): self._suspend_logs = value - if self.state["current_page"] == "terminal": + if self._current_page == "terminal": self.tabs["overview"].setChecked(True) self.tabs["terminal"].setVisible(not self._suspend_logs) @@ -882,9 +880,21 @@ class Window(QtWidgets.QDialog): visibility = True if hasattr(plugin, "hide_ui_on_process") and plugin.hide_ui_on_process: visibility = False + self._hidden_for_plugin_process = not visibility - if self.isVisible() != visibility: - self.setVisible(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 @@ -955,7 +965,7 @@ class Window(QtWidgets.QDialog): self.intent_box.setEnabled(True) # Refresh tab - self.on_tab_changed(self.state["current_page"]) + self.on_tab_changed(self._current_page) self.update_compatibility() self.button_suspend_logs.setEnabled(False) @@ -1027,8 +1037,9 @@ class Window(QtWidgets.QDialog): self._update_state() - if not self.isVisible(): - self.setVisible(True) + 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] @@ -1103,8 +1114,9 @@ class Window(QtWidgets.QDialog): plugin_item, instance_item ) - if not self.isVisible(): - self.setVisible(True) + if self._hidden_for_plugin_process: + self._hidden_for_plugin_process = False + self._ensure_visible(True) # ------------------------------------------------------------------------- # @@ -1223,53 +1235,20 @@ class Window(QtWidgets.QDialog): """ - # Make it snappy, but take care to clean it all up. - # TODO(marcus): Enable GUI to return on problem, such - # as asking whether or not the user really wants to quit - # given there are things currently running. - self.hide() + self.info(self.tr("Closing..")) - if self.state["is_closing"]: + if self.controller.is_running: + self.info(self.tr("..as soon as processing is finished..")) + self.controller.stop() - # Explicitly clear potentially referenced data - self.info(self.tr("Cleaning up models..")) - self.intent_model.deleteLater() - self.plugin_model.deleteLater() - self.terminal_model.deleteLater() - self.terminal_proxy.deleteLater() - self.plugin_proxy.deleteLater() + 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) - self.info(self.tr("Cleaning up controller..")) - self.controller.cleanup() - - self.info(self.tr("All clean!")) - self.info(self.tr("Good bye")) - return super(Window, self).closeEvent(event) - - self.info(self.tr("Closing..")) - - def on_problem(): - self.heads_up( - "Warning", "Had trouble closing down. " - "Please tell someone and try again." - ) - self.show() - - if self.controller.is_running: - self.info(self.tr("..as soon as processing is finished..")) - self.controller.stop() - self.finished.connect(self.close) - util.defer(200, on_problem) - return event.ignore() - - self.state["is_closing"] = True - - util.defer(200, self.close) - return event.ignore() + event.accept() def reject(self): """Handle ESC key""" From e54073608b59c4fe814a0fa3a1e49faef8e5a551 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Jun 2022 15:48:28 +0200 Subject: [PATCH 214/258] Nuke: multiple bake stream correct frame range on farm --- .../plugins/publish/submit_nuke_deadline.py | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index ca68c87f9a..93fb511a34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -55,8 +55,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self._ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) self._deadline_user = context.data.get( "deadlineUser", getpass.getuser()) - self._frame_start = int(instance.data["frameStartHandle"]) - self._frame_end = int(instance.data["frameEndHandle"]) + submit_frame_start = int(instance.data["frameStartHandle"]) + submit_frame_end = int(instance.data["frameEndHandle"]) # get output path render_path = instance.data['path'] @@ -82,13 +82,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # exception for slate workflow if "slate" in instance.data["families"]: - self._frame_start -= 1 + submit_frame_start -= 1 - response = self.payload_submit(instance, - script_path, - render_path, - node.name() - ) + response = self.payload_submit( + instance, + script_path, + render_path, + node.name(), + submit_frame_start, + submit_frame_end + ) # Store output dir for unified publisher (filesequence) instance.data["deadlineSubmissionJob"] = response.json() instance.data["outputDir"] = os.path.dirname( @@ -96,20 +99,22 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["publishJobState"] = "Suspended" if instance.data.get("bakingNukeScripts"): + # exception for slate workflow + if "slate" in instance.data["families"]: + submit_frame_start += 1 + for baking_script in instance.data["bakingNukeScripts"]: render_path = baking_script["bakeRenderPath"] script_path = baking_script["bakeScriptPath"] exe_node_name = baking_script["bakeWriteNodeName"] - # exception for slate workflow - if "slate" in instance.data["families"]: - self._frame_start += 1 - resp = self.payload_submit( instance, script_path, render_path, exe_node_name, + submit_frame_start, + submit_frame_end, response.json() ) @@ -126,13 +131,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): families.insert(0, "prerender") instance.data["families"] = families - def payload_submit(self, - instance, - script_path, - render_path, - exe_node_name, - responce_data=None - ): + def payload_submit( + self, + instance, + script_path, + render_path, + exe_node_name, + start_frame, + end_frame, + responce_data=None + ): render_dir = os.path.normpath(os.path.dirname(render_path)) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -192,8 +200,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Plugin": "Nuke", "Frames": "{start}-{end}".format( - start=self._frame_start, - end=self._frame_end + start=start_frame, + end=end_frame ), "Comment": self._comment, @@ -293,7 +301,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload, indent=4, sort_keys=True)) # adding expectied files to instance.data - self.expected_files(instance, render_path) + self.expected_files( + instance, + render_path, + start_frame, + end_frame + ) + self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) response = requests.post(self.deadline_url, json=payload, timeout=10) @@ -339,9 +353,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("_ path: `{}`".format(path)) return path - def expected_files(self, - instance, - path): + def expected_files( + self, + instance, + path, + start_frame, + end_frame + ): """ Create expected files in instance data """ if not instance.data.get("expectedFiles"): @@ -359,7 +377,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(path) return - for i in range(self._frame_start, (self._frame_end + 1)): + for i in range(start_frame, (end_frame + 1)): instance.data["expectedFiles"].append( os.path.join(dir, (file % i)).replace("\\", "/")) From bd01ee948cb93414be4e8ef5657bb65491d936df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:09:34 +0200 Subject: [PATCH 215/258] OP-2448 - added support for single frame playblast review Before clique.assemble returns empty collections --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 ++- openpype/plugins/publish/extract_review.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index bb1ecf279d..3e8655be7d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -111,7 +111,8 @@ class ExtractPlayblast(openpype.api.Extractor): self.log.debug("playblast path {}".format(path)) collected_files = os.listdir(stagingdir) - collections, remainder = clique.assemble(collected_files) + collections, remainder = clique.assemble(collected_files, + minimum_items=1) self.log.debug("filename {}".format(filename)) frame_collection = None diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 879125dac3..de9e3926bd 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -762,8 +762,9 @@ class ExtractReview(pyblish.api.InstancePlugin): """ start_frame = int(start_frame) end_frame = int(end_frame) - collections = clique.assemble(files)[0] - assert len(collections) == 1, "Multiple collections found." + collections = clique.assemble(files, minimum_items=1)[0] + msg = "Multiple collections {} found.".format(collections) + assert len(collections) == 1, msg col = collections[0] # do nothing if no gap is found in input range @@ -845,7 +846,7 @@ class ExtractReview(pyblish.api.InstancePlugin): dst_staging_dir = new_repre["stagingDir"] if temp_data["input_is_sequence"]: - collections = clique.assemble(repre["files"])[0] + collections = clique.assemble(repre["files"], minimum_items=1)[0] full_input_path = os.path.join( src_staging_dir, collections[0].format("{head}{padding}{tail}") From 285bfdf57e25bc4784633fb31caffb1b5962a381 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:41:10 +0200 Subject: [PATCH 216/258] OP-2448 - revert changes with minimum_review This solution might be too dangerous and have unforeseeable consequences. --- openpype/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index de9e3926bd..b6e5fee1fe 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -762,7 +762,7 @@ class ExtractReview(pyblish.api.InstancePlugin): """ start_frame = int(start_frame) end_frame = int(end_frame) - collections = clique.assemble(files, minimum_items=1)[0] + collections = clique.assemble(files)[0] msg = "Multiple collections {} found.".format(collections) assert len(collections) == 1, msg col = collections[0] @@ -846,7 +846,7 @@ class ExtractReview(pyblish.api.InstancePlugin): dst_staging_dir = new_repre["stagingDir"] if temp_data["input_is_sequence"]: - collections = clique.assemble(repre["files"], minimum_items=1)[0] + collections = clique.assemble(repre["files"])[0] full_input_path = os.path.join( src_staging_dir, collections[0].format("{head}{padding}{tail}") From fc93785ef22f3fea1226a47a37e0a6404e9c61c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:46:14 +0200 Subject: [PATCH 217/258] OP-2448 - safer variant of passing collected files Later process doesn't handle well single frame file in a list, it should be always as a regular string. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 3e8655be7d..ba939d5428 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -135,10 +135,15 @@ class ExtractPlayblast(openpype.api.Extractor): # Add camera node name to representation data camera_node_name = pm.ls(camera)[0].getTransform().name() + collected_files = list(frame_collection) + # single frame file shouldn't be in list, only as a string + if len(collected_files) == 1: + collected_files = collected_files[0] + representation = { 'name': 'png', 'ext': 'png', - 'files': list(frame_collection), + 'files': collected_files, "stagingDir": stagingdir, "frameStart": start, "frameEnd": end, From c368fb587f6bc89668ce07024154768f6de2ef7d Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:29:09 +0000 Subject: [PATCH 218/258] [Automated] Bump version --- CHANGELOG.md | 33 +++++---------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb71071205..8f9eb04f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -22,11 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) -- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) -- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) **🐛 Bug fixes** @@ -43,13 +39,13 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) -- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) **🔀 Refactored code** @@ -60,7 +56,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -68,46 +63,28 @@ **🚀 Enhancements** -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) -- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) **🐛 Bug fixes** -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) -- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) -- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) -- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) -- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) -- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) -- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.7...3.9.8) -**🚀 Enhancements** - -- nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) -- Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) - -**🐛 Bug fixes** - -- Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.6...3.9.7) diff --git a/openpype/version.py b/openpype/version.py index 2f4d180983..4c28b4a369 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.3" +__version__ = "3.11.0-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index e1b5c37289..99935b3d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.3" # OpenPype +version = "3.11.0-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 961c7b6c511765684696c6b5292e85193e24f31e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:39:35 +0000 Subject: [PATCH 219/258] [Automated] Release --- CHANGELOG.md | 10 ++++++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9eb04f45..686b34177c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) ### 📖 Documentation @@ -22,6 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** @@ -39,10 +40,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) @@ -56,6 +59,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,7 +73,6 @@ **🐛 Bug fixes** -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -78,7 +81,6 @@ **Merged pull requests:** -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 4c28b4a369..41e61b0500 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.4" +__version__ = "3.11.0" diff --git a/pyproject.toml b/pyproject.toml index 99935b3d70..2700704530 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.4" # OpenPype +version = "3.11.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 44a46382470e4e79b9e679e9395f94dacb556a70 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 18 Jun 2022 03:45:44 +0000 Subject: [PATCH 220/258] [Automated] Bump version --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 686b34177c..553102aecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,43 @@ # Changelog +## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) + +**🆕 New features** + +- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) +- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) + +**🚀 Enhancements** + +- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) +- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) +- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) +- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) + +**🐛 Bug fixes** + +- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) +- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) +- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) +- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) +- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) +- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) +- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) +- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) + +**🔀 Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) + ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) ### 📖 Documentation @@ -40,15 +75,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) -- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) **🔀 Refactored code** @@ -59,7 +91,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,10 +100,10 @@ - Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) -- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) **🐛 Bug fixes** +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -81,6 +112,7 @@ **Merged pull requests:** +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 41e61b0500..8e44b02ffc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0" +__version__ = "3.11.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 2700704530..8ba829a1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0" # OpenPype +version = "3.11.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From da1fb174bca44ec40ce00ea51b034d79bcebcca9 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 20 Jun 2022 08:48:15 +0000 Subject: [PATCH 221/258] [Automated] Release --- CHANGELOG.md | 7 +++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 553102aecf..48d0d8181e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) **🆕 New features** @@ -26,7 +26,6 @@ - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) - deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) -- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) - Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) @@ -91,6 +90,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: 21.1 fix [\#3248](https://github.com/pypeclub/OpenPype/pull/3248) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -108,7 +108,6 @@ - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 8e44b02ffc..7bf368108a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.1-nightly.1" +__version__ = "3.11.1" diff --git a/pyproject.toml b/pyproject.toml index 8ba829a1c9..ae89e7d9d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.1-nightly.1" # OpenPype +version = "3.11.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 8b1576aed2b74dd5e01842d2a87bc04c77f755f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 11:22:05 +0200 Subject: [PATCH 222/258] remove unused line --- openpype/pipeline/context_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 3e63eeba27..4a147c230b 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -88,7 +88,6 @@ def install_host(host): avalon host-interface. """ global _is_installed - global _modules_manager _is_installed = True From 20bc7300b1946e5ef082b4a43c922e108a46cabb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:14:16 +0200 Subject: [PATCH 223/258] fix delivery action --- .../modules/ftrack/event_handlers_user/action_delivery.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 47f2853820..4b799b092b 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -542,7 +542,9 @@ class Delivery(BaseAction): os.makedirs(location_path) self.log.debug("Collecting representations to process.") - version_ids = self._get_interest_version_ids(session, entities) + version_ids = self._get_interest_version_ids( + project_name, session, entities + ) repres_to_deliver = list(get_representations( project_name, representation_names=repre_names, From 665fab4e35b76b2d9cb37195fb203ff3203c1ef5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:15:32 +0200 Subject: [PATCH 224/258] fix delete asset action to filter assets right way --- .../modules/ftrack/event_handlers_user/action_delete_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 6dae3a4ca1..03d029b0c1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -134,7 +134,8 @@ class DeleteAssetSubset(BaseAction): ftrack_id = asset_doc["data"].get("ftrackId") if ftrack_id: found_ftrack_ids.add(ftrack_id) - selected_av_entities.append(asset_doc) + if ftrack_id in entity_mapping: + selected_av_entities.append(asset_doc) asset_name = asset_doc["name"] asset_docs_by_name[asset_name].append(asset_doc) From d49c2bac223f77b20710e029c6edb9caf1577d20 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:42:04 +0200 Subject: [PATCH 225/258] reverted poetry.lock changes --- poetry.lock | 197 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 83 deletions(-) diff --git a/poetry.lock b/poetry.lock index 010dd57e17..f6ccf1ffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.10.1" +version = "2.9.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" @@ -671,14 +671,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.8.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -756,11 +756,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -909,11 +909,11 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.1" +version = "3.19.4" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] name = "py" @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.2.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" +content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, - {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2202,8 +2202,8 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -2264,46 +2264,75 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2444,30 +2473,32 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, - {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, - {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, - {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, - {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, - {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, - {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, - {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, - {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, - {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, - {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, - {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, - {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, - {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, - {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, - {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, + {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, + {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, + {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, + {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, + {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, + {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, + {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, + {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, + {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, + {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, + {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, + {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, + {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, + {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, + {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, + {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, + {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, + {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2922,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, From b4c6cb414cbd9118c4c8d8a6527c14940f11b771 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:25:40 +0200 Subject: [PATCH 226/258] remove unused import --- openpype/modules/shotgrid/shotgrid_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index dcc8187194..5644f0c35f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,5 +1,4 @@ import os -import threading from openpype_interfaces import ( ITrayModule, From 151bf297c7b72788a6ff8a8f87d98c73b67d1aaf Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 13 Jun 2022 14:50:26 +0200 Subject: [PATCH 227/258] Remove unused code and add sugestions --- .../shotgrid/hooks/post_shotgrid_changes.py | 9 -------- openpype/modules/shotgrid/lib/settings.py | 23 ------------------- .../publish/collect_shotgrid_entities.py | 5 ++-- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py deleted file mode 100644 index e8369ad3cb..0000000000 --- a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py +++ /dev/null @@ -1,9 +0,0 @@ -from openpype.lib import PostLaunchHook - - -class PostShotgridHook(PostLaunchHook): - order = None - - def execute(self, *args, **kwargs): - print(args, kwargs) - pass diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4a772de5b7..924099f04b 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,24 +1,11 @@ -import os - -from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -from openpype.modules.shotgrid.lib.tools import memoize -def get_project_list(): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) - db = client['avalon'] - return db.list_collection_names() - - -@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) @@ -29,13 +16,3 @@ def get_shotgrid_servers(): def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") - - -def filter_projects_by_login(): - return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) - - -def get_shotgrid_event_mongo_info(): - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - collection_name = "shotgrid_events" - return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index a770c1eb87..9880425a41 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pymongo import MongoClient +from openpype.lib.mongo import OpenPypeMongoConnection from openpype.modules.shotgrid.lib.settings import ( get_shotgrid_project_settings, @@ -63,8 +63,7 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): def _get_shotgrid_collection(project): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) + client = OpenPypeMongoConnection.get_mongo_client() return client.get_database("shotgrid_openpype").get_collection(project) From ad0aea007a7604a02430c5a55366e458802b853e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 13:49:21 +0200 Subject: [PATCH 228/258] remove unused method --- openpype/modules/shotgrid/lib/server.py | 26 - openpype/modules/shotgrid/lib/tools.py | 16 - poetry.lock | 1009 ++++++++++------------- 3 files changed, 453 insertions(+), 598 deletions(-) delete mode 100644 openpype/modules/shotgrid/lib/server.py delete mode 100644 openpype/modules/shotgrid/lib/tools.py diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py deleted file mode 100644 index 50645d4089..0000000000 --- a/openpype/modules/shotgrid/lib/server.py +++ /dev/null @@ -1,26 +0,0 @@ -import traceback - -import requests - -from openpype.api import Logger -from openpype.modules.shotgrid.lib import ( - settings as settings_lib, -) - -_LOG = Logger().get_logger("ShotgridModule.server") - - -def find_linked_projects(email): - url = "".join( - [ - settings_lib.get_leecher_backend_url(), - "/user/", - email, - "/project-user-links", - ] - ) - try: - return requests.get(url).json() - except requests.exceptions.RequestException as e: - _LOG.error(e) - traceback.print_stack() diff --git a/openpype/modules/shotgrid/lib/tools.py b/openpype/modules/shotgrid/lib/tools.py deleted file mode 100644 index 6305e9ca50..0000000000 --- a/openpype/modules/shotgrid/lib/tools.py +++ /dev/null @@ -1,16 +0,0 @@ -from functools import wraps - - -def memoize(function): - memo = {} - - @wraps(function) - def wrapper(*args): - try: - return memo[args] - except KeyError: - rv = function(*args) - memo[args] = rv - return rv - - return wrapper diff --git a/poetry.lock b/poetry.lock index 04be8c78f2..9ae486d59a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.11.5" +version = "2.9.3" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<2" +wrapt = ">=1.11,<1.14" [[package]] name = "async-timeout" @@ -172,7 +172,7 @@ pytz = ">=2015.7" [[package]] name = "bcrypt" -version = "3.2.2" +version = "3.2.0" description = "Modern password hashing for your software and your servers" category = "main" optional = false @@ -180,6 +180,7 @@ python-versions = ">=3.6" [package.dependencies] cffi = ">=1.1" +six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -200,7 +201,7 @@ wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "5.2.0" +version = "5.0.0" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -208,11 +209,11 @@ python-versions = "~=3.7" [[package]] name = "certifi" -version = "2022.5.18.1" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = ">=3.6" +python-versions = "*" [[package]] name = "cffi" @@ -225,9 +226,17 @@ python-versions = "*" [package.dependencies] pycparser = "*" +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.0.11" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -286,21 +295,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.4.1" +version = "6.3.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" -version = "37.0.2" +version = "36.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -315,7 +324,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -337,34 +346,9 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "deprecated" -version = "1.2.13" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] - -[[package]] -name = "dill" -version = "0.3.5.1" -description = "serialize all of python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - [[package]] name = "dnspython" -version = "2.2.1" +version = "2.2.0" description = "DNS toolkit" category = "main" optional = false @@ -380,7 +364,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.17.1" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -388,7 +372,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.31.0" +version = "11.26.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -413,7 +397,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.5.0" +version = "1.4.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -467,23 +451,6 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "gazu" -version = "0.8.28" -description = "Gazu is a client for Zou, the API to store the data of your CG production." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -deprecated = "1.2.13" -python-socketio = {version = "4.6.1", extras = ["client"], markers = "python_version >= \"3.5\""} -requests = ">=2.25.1,<=2.27.1" - -[package.extras] -dev = ["wheel"] -test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] - [[package]] name = "gitdb" version = "4.0.9" @@ -497,7 +464,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.27" +version = "3.1.26" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -509,7 +476,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.8.1" +version = "2.4.0" description = "Google API client core library" category = "main" optional = false @@ -517,18 +484,18 @@ python-versions = ">=3.6" [package.dependencies] google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.56.2,<2.0dev" -protobuf = ">=3.15.0,<4.0.0dev" +googleapis-common-protos = ">=1.52.0,<2.0dev" +protobuf = ">=3.12.0" requests = ">=2.18.0,<3.0.0dev" [package.extras] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcgcp = ["grpcio-gcp (>=0.2.2)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] [[package]] name = "google-api-python-client" -version = "1.12.11" +version = "1.12.10" description = "Google API Client Library for Python" category = "main" optional = false @@ -544,7 +511,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.7.0" +version = "2.6.0" description = "Google Authentication Library" category = "main" optional = false @@ -558,7 +525,6 @@ six = ">=1.9.0" [package.extras] aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] -enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -577,21 +543,21 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.56.2" +version = "1.54.0" description = "Common protobufs used in Google APIs" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -protobuf = ">=3.15.0,<4.0.0dev" +protobuf = ">=3.12.0" [package.extras] -grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] +grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.20.4" +version = "0.20.2" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -602,11 +568,11 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "3.3" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "imagesize" @@ -618,7 +584,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.11.4" +version = "4.10.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -629,9 +595,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -671,14 +637,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.8.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -697,7 +663,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jinxed" -version = "1.2.0" +version = "1.1.0" description = "Jinxed Terminal Library" category = "main" optional = false @@ -756,11 +722,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -811,7 +777,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.11.0" +version = "2.10.1" description = "SSH2 protocol library" category = "main" optional = false @@ -843,7 +809,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.7.post1" +version = "2.3.6" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -854,27 +820,23 @@ six = "*" [[package]] name = "pillow" -version = "9.1.1" +version = "9.0.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" -[package.extras] -docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - [[package]] name = "platformdirs" -version = "2.5.2" +version = "2.4.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -913,7 +875,7 @@ version = "3.20.0" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] name = "py" @@ -996,33 +958,29 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.12.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.13.9" +version = "2.12.2" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.11.5,<=2.12.0-dev0" +astroid = ">=2.9.0,<2.10" colorama = {version = "*", markers = "sys_platform == \"win32\""} -dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" +mccabe = ">=0.6,<0.7" platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +toml = ">=0.9.2" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} -[package.extras] -testutil = ["gitpython (>3)"] - [[package]] name = "pymongo" version = "3.12.3" @@ -1073,7 +1031,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.5" +version = "8.2" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1081,39 +1039,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.5" +version = "8.2" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" -pyobjc-framework-Cocoa = ">=8.5" -pyobjc-framework-Quartz = ">=8.5" +pyobjc-core = ">=8.2" +pyobjc-framework-Cocoa = ">=8.2" +pyobjc-framework-Quartz = ">=8.2" [[package]] name = "pyobjc-framework-cocoa" -version = "8.5" +version = "8.2" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" +pyobjc-core = ">=8.2" [[package]] name = "pyobjc-framework-quartz" -version = "8.5" +version = "8.2" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" -pyobjc-framework-Cocoa = ">=8.5" +pyobjc-core = ">=8.2" +pyobjc-framework-Cocoa = ">=8.2" [[package]] name = "pyparsing" @@ -1196,39 +1154,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" -[[package]] -name = "python-engineio" -version = "3.14.2" -description = "Engine.IO server" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -asyncio_client = ["aiohttp (>=3.4)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - -[[package]] -name = "python-socketio" -version = "4.6.1" -description = "Socket.IO server" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -python-engineio = ">=3.13.0,<4" -requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} -six = ">=1.9.0" -websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} - -[package.extras] -asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - [[package]] name = "python-xlib" version = "0.31" @@ -1250,7 +1175,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2022.1" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1274,7 +1199,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.7" +version = "1.3.6" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1315,21 +1240,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.27.1" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1344,7 +1269,7 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.2" +version = "3.3.1" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false @@ -1362,21 +1287,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "shotgun-api3" -version = "3.3.3" -description = "Shotgun Python API" -category = "main" -optional = false -python-versions = "*" -develop = false - -[package.source] -type = "git" -url = "https://github.com/shotgunsoftware/python-api.git" -reference = "v3.3.3" -resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" - [[package]] name = "six" version = "1.16.0" @@ -1387,7 +1297,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.17.0" +version = "3.13.0" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1395,7 +1305,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] [[package]] name = "smmap" @@ -1415,7 +1325,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.4" +version = "2.1.2" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1423,19 +1333,18 @@ python-versions = "*" [[package]] name = "sphinx" -version = "5.0.1" +version = "3.5.3" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.19" +docutils = ">=0.12" imagesize = "*" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1443,19 +1352,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-htmlhelp = "*" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.4" +version = "0.3" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1465,22 +1374,16 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" -[package.extras] -dev = ["pre-commit"] -lint = ["black", "flake8", "pylint"] -test = ["pytest (>=3.0.0)", "pytest-cov"] - [[package]] name = "sphinx-rtd-theme" -version = "1.0.0" +version = "0.5.1" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = "*" [package.dependencies] -docutils = "<0.18" -sphinx = ">=1.6" +sphinx = "*" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1601,7 +1504,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.0" description = "A lil' TOML parser" category = "dev" optional = false @@ -1609,7 +1512,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.4" +version = "1.5.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1633,14 +1536,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1665,9 +1568,9 @@ six = "*" [[package]] name = "wrapt" -version = "1.14.1" +version = "1.13.3" description = "Module for decorators, wrappers and monkey patching." -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -1703,15 +1606,15 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.8.0" +version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" @@ -1819,8 +1722,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, - {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, + {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, + {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1847,29 +1750,28 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ - {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, - {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, - {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, - {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, + {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] cachetools = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, + {file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"}, + {file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"}, ] certifi = [ - {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, - {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -1923,9 +1825,13 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1948,71 +1854,69 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, - {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, - {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, - {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, - {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, - {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, - {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, - {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, - {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, - {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, - {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, - {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, - {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, + {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, + {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, + {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, + {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, + {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, + {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, + {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, + {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, + {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, + {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, + {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, ] cryptography = [ - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, - {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, - {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, - {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, + {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, + {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, + {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, + {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, + {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, + {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, + {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -2042,33 +1946,25 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [ - {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, - {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, -] -dill = [ - {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, - {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, -] dnspython = [ - {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, - {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, + {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, + {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, ] docutils = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] dropbox = [ - {file = "dropbox-11.31.0-py2-none-any.whl", hash = "sha256:393a99dfe30d42fd73c265b9b7d24bb21c9a961739cd097c3541e709eb2a209c"}, - {file = "dropbox-11.31.0-py3-none-any.whl", hash = "sha256:5f924102fd6464def81573320c6aa4ea9cd3368e1b1c13d838403dd4c9ffc919"}, - {file = "dropbox-11.31.0.tar.gz", hash = "sha256:f483d65b702775b9abf7b9328f702c68c6397fc01770477c6ddbfb1d858a5bcf"}, + {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, + {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, + {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, + {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2142,52 +2038,49 @@ ftrack-python-api = [ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [ - {file = "gazu-0.8.28-py2.py3-none-any.whl", hash = "sha256:ec4f7c2688a2b37ee8a77737e4e30565ad362428c3ade9046136a998c043e51c"}, -] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, - {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, + {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, + {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, ] google-api-core = [ - {file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"}, - {file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"}, + {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, + {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, - {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, + {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, + {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, ] google-auth = [ - {file = "google-auth-2.7.0.tar.gz", hash = "sha256:8a954960f852d5f19e6af14dd8e75c20159609e85d8db37e4013cc8c3824a7e1"}, - {file = "google_auth-2.7.0-py2.py3-none-any.whl", hash = "sha256:df549a1433108801b11bdcc0e312eaf0d5f0500db42f0523e4d65c78722e8475"}, + {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, + {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, - {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, + {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, + {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, ] httplib2 = [ - {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, - {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, + {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, + {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] imagesize = [ {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, + {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, + {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2202,16 +2095,16 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] jinxed = [ - {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, - {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, + {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, + {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, ] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, @@ -2264,46 +2157,75 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2376,60 +2298,57 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, - {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, + {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, + {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, - {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, + {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, + {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, ] pillow = [ - {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, - {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, - {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, - {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, - {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, - {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, - {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, - {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, - {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, - {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, - {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, - {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, - {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, - {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, - {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, + {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, + {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, + {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2528,12 +2447,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, + {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, + {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2662,40 +2581,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, - {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, - {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, - {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, - {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, + {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, + {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, + {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, + {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, + {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, + {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, + {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, + {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, - {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, - {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, - {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, - {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, + {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, + {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, + {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, + {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, + {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, + {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, + {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, - {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, - {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, - {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, - {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, + {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, + {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, + {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, + {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, + {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, + {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, + {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2720,14 +2639,6 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [ - {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, - {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, -] -python-socketio = [ - {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, - {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, -] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2736,8 +2647,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2756,8 +2667,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, - {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, + {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, + {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, ] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, @@ -2772,29 +2683,28 @@ recommonmark = [ {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] rsa = [ {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"}, {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"}, ] secretstorage = [ - {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, - {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, + {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, + {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.17.0-py2.py3-none-any.whl", hash = "sha256:0816efc43d1d2db8286e8dbcbb2e86fd0f71c206c01c521c2cb054ecb40f9ced"}, - {file = "slack_sdk-3.17.0.tar.gz", hash = "sha256:860cd0e50c454b955f14321c8c5486a47cc1e0e84116acdb009107f836752feb"}, + {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, + {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2805,20 +2715,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, - {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, + {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, + {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, ] sphinx = [ - {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, - {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, + {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, + {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, - {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, + {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, + {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, - {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, + {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, + {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2861,34 +2771,34 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, + {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, ] typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, + {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, + {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, + {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, + {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, + {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, + {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, + {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, + {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, @@ -2899,8 +2809,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2911,70 +2821,57 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -3055,6 +2952,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] From 50ec61f1b08ae6895e7817634aa50473626339b0 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 13:49:59 +0200 Subject: [PATCH 229/258] use context to get shotgrid project id --- .../plugins/publish/collect_shotgrid_entities.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 9880425a41..8af47194e6 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -23,9 +23,11 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): self.log.info(avalon_project) self.log.info(avalon_asset) - sg_project = _get_shotgrid_project(avalon_project) + sg_project = _get_shotgrid_project(context) sg_task = _get_shotgrid_task( - avalon_project, avalon_asset, avalon_task_name + avalon_project, + avalon_asset, + avalon_task_name ) sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) @@ -67,9 +69,9 @@ def _get_shotgrid_collection(project): return client.get_database("shotgrid_openpype").get_collection(project) -def _get_shotgrid_project(avalon_project): - proj_settings = get_shotgrid_project_settings(avalon_project["name"]) - shotgrid_project_id = proj_settings.get("shotgrid_project_id") +def _get_shotgrid_project(context): + shotgrid_project_id = context.data["project_settings"].get( + "shotgrid_project_id") if shotgrid_project_id: return {"type": "Project", "id": shotgrid_project_id} return {} From 43c41d2793e18dd0f8b54cdd748dbcd672e8cc35 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Jun 2022 14:04:24 +0200 Subject: [PATCH 230/258] Fix - added unc path to zifile command in Harmony Extracting too large url resulted in 'File not found' issue (side effect was that files in offending directory were skipped). UNC path seems to help. --- .../deadline/plugins/publish/submit_harmony_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 2cf502224f..a1ee5e0957 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -322,7 +322,9 @@ class HarmonySubmitDeadline( ) unzip_dir = (published_scene.parent / published_scene.stem) with _ZipFile(published_scene, "r") as zip_ref: - zip_ref.extractall(unzip_dir.as_posix()) + # UNC path (//?/) added to minimalize risk with extracting + # to large file paths + zip_ref.extractall("//?/" + str(unzip_dir.as_posix())) # find any xstage files in directory, prefer the one with the same name # as directory (plus extension) From 47d2a611e9046cb2c87654f3d5d405cb222a0c57 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:14:13 +0200 Subject: [PATCH 231/258] update poetry lock --- poetry.lock | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/poetry.lock b/poetry.lock index 9ae486d59a..23a70ffb0b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1287,6 +1287,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "shotgun-api3" +version = "3.3.3" +description = "Shotgun Python API" +category = "main" +optional = false +python-versions = "*" +develop = false + +[package.source] +type = "git" +url = "https://github.com/shotgunsoftware/python-api.git" +reference = "v3.3.3" +resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" + [[package]] name = "six" version = "1.16.0" From ad3380b3a01c8ba3d97c2e2c4dc8ba54da83403c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:17:57 +0200 Subject: [PATCH 232/258] remove unused import --- .../shotgrid/plugins/publish/collect_shotgrid_entities.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 8af47194e6..0b03ac2e5d 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -3,10 +3,6 @@ import os import pyblish.api from openpype.lib.mongo import OpenPypeMongoConnection -from openpype.modules.shotgrid.lib.settings import ( - get_shotgrid_project_settings, -) - class CollectShotgridEntities(pyblish.api.ContextPlugin): """Collect shotgrid entities according to the current context""" From 0417fb17ca38bc79e60b4c7aa70f111cee217f6f Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:48:00 +0200 Subject: [PATCH 233/258] poetry lock rebase --- poetry.lock | 868 +++++++++++++++++++++++++++++----------------------- 1 file changed, 492 insertions(+), 376 deletions(-) diff --git a/poetry.lock b/poetry.lock index 810fa50b90..3e6620b4a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.9.3" +version = "2.11.5" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.14" +wrapt = ">=1.11,<2" [[package]] name = "async-timeout" @@ -172,7 +172,7 @@ pytz = ">=2015.7" [[package]] name = "bcrypt" -version = "3.2.0" +version = "3.2.2" description = "Modern password hashing for your software and your servers" category = "main" optional = false @@ -180,7 +180,6 @@ python-versions = ">=3.6" [package.dependencies] cffi = ">=1.1" -six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -201,7 +200,7 @@ wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "5.0.0" +version = "5.2.0" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -209,11 +208,11 @@ python-versions = "~=3.7" [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.5.18.1" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" @@ -226,17 +225,9 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -295,21 +286,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.4.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" -version = "36.0.1" +version = "37.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -324,7 +315,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -346,9 +337,34 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "dill" +version = "0.3.5.1" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "dnspython" -version = "2.2.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = false @@ -364,7 +380,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -372,7 +388,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.26.0" +version = "11.31.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -397,7 +413,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.4.0" +version = "1.5.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -451,6 +467,23 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "gazu" +version = "0.8.28" +description = "Gazu is a client for Zou, the API to store the data of your CG production." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +deprecated = "1.2.13" +python-socketio = {version = "4.6.1", extras = ["client"], markers = "python_version >= \"3.5\""} +requests = ">=2.25.1,<=2.27.1" + +[package.extras] +dev = ["wheel"] +test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] + [[package]] name = "gitdb" version = "4.0.9" @@ -464,7 +497,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -476,7 +509,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.4.0" +version = "2.8.1" description = "Google API client core library" category = "main" optional = false @@ -484,18 +517,18 @@ python-versions = ">=3.6" [package.dependencies] google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.52.0,<2.0dev" -protobuf = ">=3.12.0" +googleapis-common-protos = ">=1.56.2,<2.0dev" +protobuf = ">=3.15.0,<4.0.0dev" requests = ">=2.18.0,<3.0.0dev" [package.extras] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] [[package]] name = "google-api-python-client" -version = "1.12.10" +version = "1.12.11" description = "Google API Client Library for Python" category = "main" optional = false @@ -511,7 +544,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.6.0" +version = "2.7.0" description = "Google Authentication Library" category = "main" optional = false @@ -525,6 +558,7 @@ six = ">=1.9.0" [package.extras] aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] +enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -543,21 +577,21 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.54.0" +version = "1.56.2" description = "Common protobufs used in Google APIs" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -protobuf = ">=3.12.0" +protobuf = ">=3.15.0,<4.0.0dev" [package.extras] -grpc = ["grpcio (>=1.0.0)"] +grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] [[package]] name = "httplib2" -version = "0.20.2" +version = "0.20.4" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -568,11 +602,11 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" @@ -584,7 +618,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.4" description = "Read metadata from Python packages" category = "main" optional = false @@ -595,9 +629,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -663,7 +697,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jinxed" -version = "1.1.0" +version = "1.2.0" description = "Jinxed Terminal Library" category = "main" optional = false @@ -777,7 +811,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.10.1" +version = "2.11.0" description = "SSH2 protocol library" category = "main" optional = false @@ -809,7 +843,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -820,23 +854,27 @@ six = "*" [[package]] name = "pillow" -version = "9.0.1" +version = "9.1.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -958,29 +996,33 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.12.2" +version = "2.13.9" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.9.0,<2.10" +astroid = ">=2.11.5,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" +mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" -toml = ">=0.9.2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +[package.extras] +testutil = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "3.12.3" @@ -1031,7 +1073,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.2" +version = "8.5" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1039,39 +1081,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.2" +version = "8.5" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" -pyobjc-framework-Quartz = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" +pyobjc-framework-Quartz = ">=8.5" [[package]] name = "pyobjc-framework-cocoa" -version = "8.2" +version = "8.5" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" +pyobjc-core = ">=8.5" [[package]] name = "pyobjc-framework-quartz" -version = "8.2" +version = "8.5" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" [[package]] name = "pyparsing" @@ -1154,6 +1196,39 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-engineio" +version = "3.14.2" +description = "Engine.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "4.6.1" +description = "Socket.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-engineio = ">=3.13.0,<4" +requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} +six = ">=1.9.0" +websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + [[package]] name = "python-xlib" version = "0.31" @@ -1175,7 +1250,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1199,7 +1274,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.6" +version = "1.3.7" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1240,21 +1315,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1269,7 +1344,7 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.1" +version = "3.3.2" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false @@ -1312,7 +1387,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.13.0" +version = "3.17.0" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1320,7 +1395,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1340,7 +1415,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.2" +version = "2.1.4" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1348,18 +1423,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.3" +version = "5.0.1" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.19" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1367,19 +1443,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.3" +version = "0.4" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1389,16 +1465,22 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" +[package.extras] +dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] -sphinx = "*" +docutils = "<0.18" +sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1519,7 +1601,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false @@ -1527,7 +1609,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1551,14 +1633,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1583,9 +1665,9 @@ six = "*" [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -1621,15 +1703,15 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" @@ -1737,8 +1819,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, + {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, + {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1765,28 +1847,29 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, - {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, - {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, - {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, - {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, ] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] cachetools = [ - {file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"}, - {file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"}, + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, + {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -1840,13 +1923,9 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1869,69 +1948,71 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, ] cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, + {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, + {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, + {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -1961,25 +2042,33 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] dnspython = [ - {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, - {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] dropbox = [ - {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, - {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, - {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, + {file = "dropbox-11.31.0-py2-none-any.whl", hash = "sha256:393a99dfe30d42fd73c265b9b7d24bb21c9a961739cd097c3541e709eb2a209c"}, + {file = "dropbox-11.31.0-py3-none-any.whl", hash = "sha256:5f924102fd6464def81573320c6aa4ea9cd3368e1b1c13d838403dd4c9ffc919"}, + {file = "dropbox-11.31.0.tar.gz", hash = "sha256:f483d65b702775b9abf7b9328f702c68c6397fc01770477c6ddbfb1d858a5bcf"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, + {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2053,49 +2142,52 @@ ftrack-python-api = [ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] +gazu = [ + {file = "gazu-0.8.28-py2.py3-none-any.whl", hash = "sha256:ec4f7c2688a2b37ee8a77737e4e30565ad362428c3ade9046136a998c043e51c"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] google-api-core = [ - {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, - {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, + {file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"}, + {file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, - {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, + {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, + {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] google-auth = [ - {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, - {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, + {file = "google-auth-2.7.0.tar.gz", hash = "sha256:8a954960f852d5f19e6af14dd8e75c20159609e85d8db37e4013cc8c3824a7e1"}, + {file = "google_auth-2.7.0-py2.py3-none-any.whl", hash = "sha256:df549a1433108801b11bdcc0e312eaf0d5f0500db42f0523e4d65c78722e8475"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, - {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, + {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, + {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, ] httplib2 = [ - {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, - {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, + {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, + {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2118,8 +2210,8 @@ jinja2 = [ {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] jinxed = [ - {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, - {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, + {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, + {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, ] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, @@ -2313,57 +2405,60 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, - {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pillow = [ - {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, - {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, - {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, - {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, - {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, - {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, - {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, - {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, - {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, - {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, - {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, - {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, - {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, - {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, + {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, + {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, + {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, + {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, + {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, + {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, + {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, + {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, + {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, + {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, + {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, + {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, + {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2464,12 +2559,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2598,40 +2693,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, - {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, - {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, - {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, - {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, + {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, + {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, + {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, + {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, + {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, + {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, - {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, - {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, - {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, - {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, + {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, + {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, + {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, + {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, + {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, - {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, - {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, - {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, - {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, + {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, + {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, + {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, + {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, + {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2656,6 +2751,14 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-engineio = [ + {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, + {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, +] +python-socketio = [ + {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, + {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, +] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2664,8 +2767,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2684,8 +2787,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, - {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, + {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, + {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, ] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, @@ -2700,16 +2803,16 @@ recommonmark = [ {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rsa = [ {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"}, {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"}, ] secretstorage = [ - {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, - {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, @@ -2720,8 +2823,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, - {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, + {file = "slack_sdk-3.17.0-py2.py3-none-any.whl", hash = "sha256:0816efc43d1d2db8286e8dbcbb2e86fd0f71c206c01c521c2cb054ecb40f9ced"}, + {file = "slack_sdk-3.17.0.tar.gz", hash = "sha256:860cd0e50c454b955f14321c8c5486a47cc1e0e84116acdb009107f836752feb"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2732,20 +2835,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, - {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, + {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, + {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, - {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2788,34 +2891,34 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, @@ -2826,8 +2929,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2838,57 +2941,70 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -2969,6 +3085,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From e5fbdd1d43ee2a51a0bfec38d3859c5b8b20a080 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 15:11:26 +0200 Subject: [PATCH 234/258] added shotgun-api3 source files to poetry lock --- poetry.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/poetry.lock b/poetry.lock index 3e6620b4a2..f6ccf1ffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2818,6 +2818,7 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, From 8ac3ed77d55def1ea674009cf630783059ea7823 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 15:19:26 +0200 Subject: [PATCH 235/258] Nuke: fix none existing rendered files error addressing the comment https://github.com/pypeclub/OpenPype/pull/3245#pullrequestreview-1004589515 --- .../plugins/publish/extract_slate_frame.py | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 87cb333ae1..e0c4bdb953 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -74,6 +74,30 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.info( "StagingDir `{0}`...".format(instance.data["stagingDir"])) + def _check_frames_exists(self, instance): + # rendering path from group write node + fpath = instance.data["path"] + + # instance frame range with handles + first = instance.data["frameStartHandle"] + last = instance.data["frameEndHandle"] + + padding = fpath.count('#') + + test_path_template = fpath + if padding: + repl_string = "#" * padding + test_path_template = fpath.replace( + repl_string, "%0{}d".format(padding)) + + for frame in range(first, last + 1): + test_file = test_path_template % frame + if not os.path.exists(test_file): + self.log.debug("__ test_file: `{}`".format(test_file)) + return None + + return True + def render_slate( self, instance, @@ -128,16 +152,21 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) - # Read node - r_node = nuke.createNode("Read") - r_node["file"].setValue(fpath) - r_node["first"].setValue(first_frame) - r_node["origfirst"].setValue(first_frame) - r_node["last"].setValue(last_frame) - r_node["origlast"].setValue(last_frame) - r_node["colorspace"].setValue(instance.data["colorspace"]) - previous_node = r_node - temporary_nodes = [previous_node] + # fallback if files does not exists + if self._check_frames_exists(instance): + # Read node + r_node = nuke.createNode("Read") + r_node["file"].setValue(fpath) + r_node["first"].setValue(first_frame) + r_node["origfirst"].setValue(first_frame) + r_node["last"].setValue(last_frame) + r_node["origlast"].setValue(last_frame) + r_node["colorspace"].setValue(instance.data["colorspace"]) + previous_node = r_node + temporary_nodes = [previous_node] + else: + previous_node = slate_node.dependencies().pop() + temporary_nodes = [] # only create colorspace baking if toggled on if bake_viewer_process: From 497e4c0cca40ee8e39b03ec00c08a594ffc76dbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:31:29 +0200 Subject: [PATCH 236/258] use query functions in aftereffects --- .../aftereffects/plugins/create/workfile_creator.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index 88e55e21b5..badb3675fd 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -1,4 +1,5 @@ import openpype.hosts.aftereffects.api as api +from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, CreatedInstance, @@ -41,10 +42,7 @@ class AEWorkfileCreator(AutoCreator): host_name = legacy_io.Session["AVALON_APP"] if existing_instance is None: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) @@ -69,10 +67,7 @@ class AEWorkfileCreator(AutoCreator): existing_instance["asset"] != asset_name or existing_instance["task"] != task_name ): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) From b9a67fb80117e980462222f733df5f64968b16b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:43:14 +0200 Subject: [PATCH 237/258] pass asset document to 'get_asset_settings' --- openpype/hosts/aftereffects/api/pipeline.py | 4 ++-- .../plugins/publish/validate_scene_settings.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index a428a1470d..0bc47665b0 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -65,14 +65,14 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): instance[0].Visible = new_value -def get_asset_settings(): +def get_asset_settings(asset_doc): """Get settings on current asset from database. Returns: dict: Scene data. """ - asset_data = lib.get_asset()["data"] + asset_data = asset_doc["data"] fps = asset_data.get("fps") frame_start = asset_data.get("frameStart") frame_end = asset_data.get("frameEnd") diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 6fe63fc41e..78f98d7445 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- -"""Validate scene settings.""" +"""Validate scene settings. +Requires: + instance -> assetEntity + instance -> anatomyData +""" import os import re @@ -67,7 +71,8 @@ class ValidateSceneSettings(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return - expected_settings = get_asset_settings() + asset_doc = instance.data["assetEntity"] + expected_settings = get_asset_settings(asset_doc) self.log.info("config from DB::{}".format(expected_settings)) task_name = instance.data["anatomyData"]["task"]["name"] From d39481042dd7fde38a57acf466da8a23fe877ad3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:50:26 +0200 Subject: [PATCH 238/258] use query functions in photoshop creator --- .../photoshop/plugins/create/workfile_creator.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index 875a9b8a94..43302329f1 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -1,4 +1,5 @@ import openpype.hosts.photoshop.api as api +from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, CreatedInstance, @@ -40,10 +41,7 @@ class PSWorkfileCreator(AutoCreator): task_name = legacy_io.Session["AVALON_TASK"] host_name = legacy_io.Session["AVALON_APP"] if existing_instance is None: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) @@ -67,10 +65,7 @@ class PSWorkfileCreator(AutoCreator): existing_instance["asset"] != asset_name or existing_instance["task"] != task_name ): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) From 144ef08260f58fcbc3eff56f80073db46432c055 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Tue, 21 Jun 2022 08:29:04 +0200 Subject: [PATCH 239/258] change default settings --- openpype/settings/defaults/system_settings/modules.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index d55691d7a2..9d8910689a 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -132,8 +132,7 @@ } }, "shotgrid": { - "enabled": true, - "filter_projects_by_login": true, + "enabled": false, "leecher_manager_url": "http://127.0.0.1:3000", "leecher_backend_url": "http://127.0.0.1:8090", "shotgrid_settings": {} From fa8d37d9b68ebb1c1ac85ec6bd8c00fd348e4f80 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 10:55:07 +0200 Subject: [PATCH 240/258] use query functions in harmony --- openpype/hosts/harmony/api/README.md | 3 ++- openpype/hosts/harmony/api/pipeline.py | 18 ++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/harmony/api/README.md b/openpype/hosts/harmony/api/README.md index dd45eb14dd..b39f900886 100644 --- a/openpype/hosts/harmony/api/README.md +++ b/openpype/hosts/harmony/api/README.md @@ -610,7 +610,8 @@ class ImageSequenceLoader(load.LoaderPlugin): def update(self, container, representation): node = container.pop("node") - version = legacy_io.find_one({"_id": representation["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id(project_name, representation["parent"]) files = [] for f in version["data"]["files"]: files.append( diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index b953d0e984..86b5753f7e 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -2,10 +2,10 @@ import os from pathlib import Path import logging -from bson.objectid import ObjectId import pyblish.api from openpype import lib +from openpype.client import get_representation_by_id from openpype.lib import register_event_callback from openpype.pipeline import ( legacy_io, @@ -104,22 +104,20 @@ def check_inventory(): If it does it will colorize outdated nodes and display warning message in Harmony. """ - if not lib.any_outdated(): - return + project_name = legacy_io.active_project() outdated_containers = [] for container in ls(): - representation = container['representation'] - representation_doc = legacy_io.find_one( - { - "_id": ObjectId(representation), - "type": "representation" - }, - projection={"parent": True} + representation_id = container['representation'] + representation_doc = get_representation_by_id( + project_name, representation_id, fields=["parent"] ) if representation_doc and not lib.is_latest(representation_doc): outdated_containers.append(container) + if not outdated_containers: + return + # Colour nodes. outdated_nodes = [] for container in outdated_containers: From 2c7c10a404400903089ab561e85661d2f84fb0bd Mon Sep 17 00:00:00 2001 From: murphy Date: Tue, 21 Jun 2022 12:03:31 +0200 Subject: [PATCH 241/258] vray device aspect ratio fix Vray has different attribute name for device aspect ratio from other renderers. (.aspectRatio instead of .deviceAspectRatio) Testing - open/create a maya scene with vray renderer set - run OpenPype -> Set Resolution resolution should be set without an error message about nonexistent vray attribute --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index bce03a648b..d5763160bc 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2141,7 +2141,7 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.height" % control_node, height) deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) - cmds.setAttr("%s.deviceAspectRatio" % control_node, deviceAspectRatio) + cmds.setAttr("%s.aspectRatio" % control_node, deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 861fbee46c36762f50bffbe9aa00a9a60af30cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:14:39 +0200 Subject: [PATCH 242/258] :ambulance: limit attribute name only to vray --- openpype/hosts/maya/api/lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index d5763160bc..3e239d5361 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2126,9 +2126,11 @@ def set_scene_resolution(width, height, pixelAspect): control_node = "defaultResolution" current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") + aspect_ration_attr = "deviceAspectRatio" # Give VRay a helping hand as it is slightly different from the rest if current_renderer == "vray": + aspect_ration_attr = "aspectRatio" vray_node = "vraySettings" if cmds.objExists(vray_node): control_node = vray_node @@ -2141,7 +2143,8 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.height" % control_node, height) deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) - cmds.setAttr("%s.aspectRatio" % control_node, deviceAspectRatio) + cmds.setAttr( + "{}.{}".format(control_node, aspect_ration_attr), deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 94af9cdcc949ee4b84cb2cedc4d35cec9e47af11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:16:00 +0200 Subject: [PATCH 243/258] :pencil2: fix typo in variable name --- openpype/hosts/maya/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 3e239d5361..92a3efcd1f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2126,11 +2126,11 @@ def set_scene_resolution(width, height, pixelAspect): control_node = "defaultResolution" current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - aspect_ration_attr = "deviceAspectRatio" + aspect_ratio_attr = "deviceAspectRatio" # Give VRay a helping hand as it is slightly different from the rest if current_renderer == "vray": - aspect_ration_attr = "aspectRatio" + aspect_ratio_attr = "aspectRatio" vray_node = "vraySettings" if cmds.objExists(vray_node): control_node = vray_node @@ -2144,7 +2144,7 @@ def set_scene_resolution(width, height, pixelAspect): deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) cmds.setAttr( - "{}.{}".format(control_node, aspect_ration_attr), deviceAspectRatio) + "{}.{}".format(control_node, aspect_ratio_attr), deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 783f07e616a8a9749f2f37e44870a37c99719317 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 14:35:06 +0200 Subject: [PATCH 244/258] make sure exit code is set to not None --- openpype/hosts/tvpaint/api/communication_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index 65cb9aa2f3..6ac3e6324c 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -707,6 +707,9 @@ class BaseCommunicator: if exit_code is not None: self.exit_code = exit_code + if self.exit_code is None: + self.exit_code = 0 + def stop(self): """Stop communication and currently running python process.""" log.info("Stopping communication") From 28d7e2ab517f0a311b689bb18582dcd737308ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:50:59 +0200 Subject: [PATCH 245/258] :recycle: clean up some superfluous output --- .../hosts/maya/plugins/publish/validate_camera_contents.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index eb93245f93..87712a4cea 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -51,18 +51,17 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") if not cls.validate_shapes: - cls.log.info("not validating shapes in the content") + cls.log.info("Not validating shapes in the content.") for member in members: parents = cmds.ls(member, long=True)[0].split("|")[1:-1] - cls.log.info(parents) parents_long_named = [ "|".join(parents[:i]) for i in range(1, 1 + len(parents)) ] - cls.log.info(parents_long_named) if cameras[0] in parents_long_named: cls.log.error( - "{} is parented under camera {}".format(member, cameras[0])) + "{} is parented under camera {}".format( + member, cameras[0])) invalid.extend(member) return invalid From 9136af76fadf21af6ce6b93292809174f95f70a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 15:06:00 +0200 Subject: [PATCH 246/258] :recycle: simplify prefix reduction --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 5875e65e47..1dab3274a0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -218,11 +218,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default_prefix = re.sub( cls.R_AOV_TOKEN, "", default_prefix) # remove aov token from prefix to pass validation - # first resolve it and then remove if dangling - default_prefix = default_prefix.replace( - "{aov_separator}", instance.data.get("aovSeparator", "_")) - default_prefix = default_prefix.rstrip( - instance.data.get("aovSeparator", "_")) + default_prefix = default_prefix.split("{aov_separator}")[0] elif not re.search(cls.R_AOV_TOKEN, prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " From d08316d19ce5c097e3740cffd1163cd85f51cad5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:25:23 +0200 Subject: [PATCH 247/258] generate uuids is using query functions and before that tries to look for asset entity --- openpype/hosts/maya/api/action.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py index ca1006b6aa..90605734e7 100644 --- a/openpype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -3,6 +3,7 @@ from __future__ import absolute_import import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io from openpype.api import get_errored_instances_from_context @@ -74,12 +75,21 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): from . import lib - asset = instance.data['asset'] - asset_id = legacy_io.find_one( - {"name": asset, "type": "asset"}, - projection={"_id": True} - )['_id'] - for node, _id in lib.generate_ids(nodes, asset_id=asset_id): + # Expecting this is called on validators in which case 'assetEntity' + # should be always available, but kept a way to query it by name. + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + asset_name = instance.data["asset"] + project_name = legacy_io.active_project() + self.log.info(( + "Asset is not stored on instance." + " Querying by name \"{}\" from project \"{}\"" + ).format(asset_name, project_name)) + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] + ) + + for node, _id in lib.generate_ids(nodes, asset_id=asset_doc["_id"]): lib.set_id(node, _id, overwrite=True) From c9cfcd2b8025e64d02b5b49c99707ce07eaac9ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:26:02 +0200 Subject: [PATCH 248/258] use query functions in commands and lib --- openpype/hosts/maya/api/commands.py | 9 +- openpype/hosts/maya/api/lib.py | 132 +++++++++++++++------------- 2 files changed, 79 insertions(+), 62 deletions(-) diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index dd616b6dd6..355edf3ae4 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -2,6 +2,7 @@ """OpenPype script commands to be used directly in Maya.""" from maya import cmds +from openpype.client import get_asset_by_name, get_project from openpype.pipeline import legacy_io @@ -79,8 +80,9 @@ def reset_frame_range(): cmds.currentUnit(time=fps) # Set frame start/end + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset = legacy_io.find_one({"name": asset_name, "type": "asset"}) + asset = get_asset_by_name(project_name, asset_name) frame_start = asset["data"].get("frameStart") frame_end = asset["data"].get("frameEnd") @@ -145,8 +147,9 @@ def reset_resolution(): resolution_height = 1080 # Get resolution from asset + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({"name": asset_name, "type": "asset"}) + asset_doc = get_asset_by_name(project_name, asset_name) resolution = _resolution_from_document(asset_doc) # Try get resolution from project if resolution is None: @@ -155,7 +158,7 @@ def reset_resolution(): "Asset \"{}\" does not have set resolution." " Trying to get resolution from project" ).format(asset_name)) - project_doc = legacy_io.find_one({"type": "project"}) + project_doc = get_project(project_name) resolution = _resolution_from_document(project_doc) if resolution is None: diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index bce03a648b..96d98aa6ae 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -12,11 +12,18 @@ import contextlib from collections import OrderedDict, defaultdict from math import ceil from six import string_types -import bson from maya import cmds, mel import maya.api.OpenMaya as om +from openpype.client import ( + get_project, + get_asset_by_name, + get_subsets, + get_subset_by_name, + get_last_versions, + get_representation_by_name +) from openpype import lib from openpype.api import get_anatomy_settings from openpype.pipeline import ( @@ -1387,15 +1394,11 @@ def generate_ids(nodes, asset_id=None): if asset_id is None: # Get the asset ID from the database for the asset of current context - asset_data = legacy_io.find_one( - { - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }, - projection={"_id": True} - ) - assert asset_data, "No current asset found in Session" - asset_id = asset_data['_id'] + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + asset_doc = get_asset_by_name(project_name, asset_name, fields=["_id"]) + assert asset_doc, "No current asset found in Session" + asset_id = asset_doc['_id'] node_ids = [] for node in nodes: @@ -1548,13 +1551,13 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset - subset = legacy_io.find({ - "parent": bson.ObjectId(asset_id), - "type": "subset", - "name": {"$regex": "look*"} - }) - - return list(subset) + project_name = legacy_io.active_project() + subset_docs = get_subsets(project_name, asset_ids=[asset_id]) + return [ + subset_doc + for subset_doc in subset_docs + if subset_doc["name"].startswith("look") + ] def assign_look_by_version(nodes, version_id): @@ -1570,18 +1573,15 @@ def assign_look_by_version(nodes, version_id): None """ - # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + project_name = legacy_io.active_project() - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + # Get representations of shader file and relationships + look_representation = get_representation_by_name( + project_name, "ma", version_id + ) + json_representation = get_representation_by_name( + project_name, "json", version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -1639,42 +1639,54 @@ def assign_look(nodes, subset="lookDefault"): parts = pype_id.split(":", 1) grouped[parts[0]].append(node) + project_name = legacy_io.active_project() + subset_docs = get_subsets( + project_name, subset_names=[subset], asset_ids=grouped.keys() + ) + subset_docs_by_asset_id = { + str(subset_doc["parent"]): subset_doc + for subset_doc in subset_docs + } + subset_ids = { + subset_doc["_id"] + for subset_doc in subset_docs_by_asset_id.values() + } + last_version_docs = get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "name", "data.families"] + ) + last_version_docs_by_subset_id = { + last_version_doc["parent"]: last_version_doc + for last_version_doc in last_version_docs + } + for asset_id, asset_nodes in grouped.items(): # create objectId for database - try: - asset_id = bson.ObjectId(asset_id) - except bson.errors.InvalidId: - log.warning("Asset ID is not compatible with bson") - continue - subset_data = legacy_io.find_one({ - "type": "subset", - "name": subset, - "parent": asset_id - }) - - if not subset_data: + subset_doc = subset_docs_by_asset_id.get(asset_id) + if not subset_doc: log.warning("No subset '{}' found for {}".format(subset, asset_id)) continue - # get last version - # with backwards compatibility - version = legacy_io.find_one( - { - "parent": subset_data['_id'], - "type": "version", - "data.families": {"$in": ["look"]} - }, - sort=[("name", -1)], - projection={ - "_id": True, - "name": True - } - ) + last_version = last_version_docs_by_subset_id.get(subset_doc["_id"]) + if not last_version: + log.warning(( + "Not found last version for subset '{}' on asset with id {}" + ).format(subset, asset_id)) + continue - log.debug("Assigning look '{}' ".format(subset, - version["name"])) + families = last_version.get("data", {}).get("families") or [] + if "look" not in families: + log.warning(( + "Last version for subset '{}' on asset with id {}" + " does not have look family" + ).format(subset, asset_id)) + continue - assign_look_by_version(asset_nodes, version['_id']) + log.debug("Assigning look '{}' ".format( + subset, last_version["name"])) + + assign_look_by_version(asset_nodes, last_version["_id"]) def apply_shaders(relationships, shadernodes, nodes): @@ -2155,7 +2167,8 @@ def reset_scene_resolution(): None """ - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + project_doc = get_project(project_name) project_data = project_doc["data"] asset_data = lib.get_asset()["data"] @@ -2188,7 +2201,8 @@ def set_context_settings(): """ # Todo (Wijnand): apply renderer and resolution of project - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + project_doc = get_project(project_name) project_data = project_doc["data"] asset_data = lib.get_asset()["data"] From 8cd440e99f69a3dfe031bf48fbba69d6b10f7727 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:29:34 +0200 Subject: [PATCH 249/258] use query functions in setdress --- openpype/hosts/maya/api/setdress.py | 66 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index f8d3ed79b8..bea8f154b1 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -6,10 +6,16 @@ import contextlib import copy import six -from bson.objectid import ObjectId from maya import cmds +from openpype.client import ( + get_version_by_name, + get_last_version_by_subset_id, + get_representation_by_id, + get_representation_by_name, + get_representation_parents, +) from openpype.pipeline import ( schema, legacy_io, @@ -283,36 +289,35 @@ def update_package_version(container, version): """ # Versioning (from `core.maya.pipeline`) - current_representation = legacy_io.find_one({ - "_id": ObjectId(container["representation"]) - }) + project_name = legacy_io.active_project() + current_representation = get_representation_by_id( + project_name, container["representation"] + ) assert current_representation is not None, "This is a bug" - version_, subset, asset, project = legacy_io.parenthood( - current_representation + repre_parents = get_representation_parents( + project_name, current_representation ) + version_doc = subset_doc = asset_doc = project_doc = None + if repre_parents: + version_doc, subset_doc, asset_doc, project_doc = repre_parents if version == -1: - new_version = legacy_io.find_one({ - "type": "version", - "parent": subset["_id"] - }, sort=[("name", -1)]) + new_version = get_last_version_by_subset_id( + project_name, subset_doc["_id"] + ) else: - new_version = legacy_io.find_one({ - "type": "version", - "parent": subset["_id"], - "name": version, - }) + new_version = get_version_by_name( + project_name, version, subset_doc["_id"] + ) assert new_version is not None, "This is a bug" # Get the new representation (new file) - new_representation = legacy_io.find_one({ - "type": "representation", - "parent": new_version["_id"], - "name": current_representation["name"] - }) + new_representation = get_representation_by_name( + project_name, current_representation["name"], new_version["_id"] + ) update_package(container, new_representation) @@ -330,10 +335,10 @@ def update_package(set_container, representation): """ # Load the original package data - current_representation = legacy_io.find_one({ - "_id": ObjectId(set_container['representation']), - "type": "representation" - }) + project_name = legacy_io.active_project() + current_representation = get_representation_by_id( + project_name, set_container["representation"] + ) current_file = get_representation_path(current_representation) assert current_file.endswith(".json") @@ -380,6 +385,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): from openpype.hosts.maya.lib import DEFAULT_MATRIX, get_container_transforms set_namespace = set_container['namespace'] + project_name = legacy_io.active_project() # Update the setdress hierarchy alembic set_root = get_container_transforms(set_container, root=True) @@ -481,12 +487,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file): # Check whether the conversion can be done by the Loader. # They *must* use the same asset, subset and Loader for # `update_container` to make sense. - old = legacy_io.find_one({ - "_id": ObjectId(representation_current) - }) - new = legacy_io.find_one({ - "_id": ObjectId(representation_new) - }) + old = get_representation_by_id( + project_name, representation_current + ) + new = get_representation_by_id( + project_name, representation_new + ) is_valid = compare_representations(old=old, new=new) if not is_valid: log.error("Skipping: %s. See log for details.", From d4d2f8fe3e9a23ed39cbaa27802f8cb48d09c65f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:30:30 +0200 Subject: [PATCH 250/258] added comment to look queries --- openpype/hosts/maya/api/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 96d98aa6ae..6863edf3e0 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1551,6 +1551,8 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset + # TODO this should probably look for family 'look' instead of checking + # subset name that can not start with family project_name = legacy_io.active_project() subset_docs = get_subsets(project_name, asset_ids=[asset_id]) return [ From a78381265e31f5bb27ef10006b1e4c0c7f869a7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:31:14 +0200 Subject: [PATCH 251/258] use query functions in maya plugins --- .../plugins/inventory/import_modelrender.py | 53 ++++++++++++------- .../hosts/maya/plugins/load/load_audio.py | 18 +++++-- .../maya/plugins/load/load_image_plane.py | 18 +++++-- openpype/hosts/maya/plugins/load/load_look.py | 10 ++-- .../hosts/maya/plugins/load/load_vrayproxy.py | 11 ++-- .../maya/plugins/publish/collect_review.py | 15 ++++-- .../publish/validate_node_ids_in_database.py | 9 +++- .../publish/validate_node_ids_related.py | 11 +--- .../publish/validate_renderlayer_aovs.py | 23 ++++---- 9 files changed, 101 insertions(+), 67 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index a5367f16e5..8a7390bc8d 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -1,6 +1,10 @@ +import re import json -from bson.objectid import ObjectId +from openpype.client import ( + get_representation_by_id, + get_representations +) from openpype.pipeline import ( InventoryAction, get_representation_context, @@ -31,6 +35,7 @@ class ImportModelRender(InventoryAction): def process(self, containers): from maya import cmds + project_name = legacy_io.active_project() for container in containers: con_name = container["objectName"] nodes = [] @@ -40,9 +45,9 @@ class ImportModelRender(InventoryAction): else: nodes.append(n) - repr_doc = legacy_io.find_one({ - "_id": ObjectId(container["representation"]), - }) + repr_doc = get_representation_by_id( + project_name, container["representation"], fields=["parent"] + ) version_id = repr_doc["parent"] print("Importing render sets for model %r" % con_name) @@ -63,26 +68,38 @@ class ImportModelRender(InventoryAction): from maya import cmds + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, version_ids=[version_id], fields=["_id", "name"] + ) # Get representations of shader file and relationships - look_repr = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": {"$regex": self.scene_type_regex}, - }) - if not look_repr: + json_repre = None + look_repres = [] + scene_type_regex = re.compile(self.scene_type_regex) + for repre_doc in repre_docs: + repre_name = repre_doc["name"] + if repre_name == self.look_data_type: + json_repre = repre_doc + continue + + if scene_type_regex.fullmatch(repre_name): + look_repres.append(repre_doc) + + # QUESTION should we care if there is more then one look + # representation? (since it's based on regex match) + look_repre = None + if look_repres: + look_repre = look_repres[0] + + # QUESTION shouldn't be json representation validated too? + if not look_repre: print("No model render sets for this model version..") return - json_repr = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": self.look_data_type, - }) - - context = get_representation_context(look_repr["_id"]) + context = get_representation_context(look_repre["_id"]) maya_file = self.filepath_from_context(context) - context = get_representation_context(json_repr["_id"]) + context = get_representation_context(json_repre["_id"]) json_file = self.filepath_from_context(context) # Import the look file diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index ce814e1299..6f60cb5726 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -1,5 +1,10 @@ from maya import cmds, mel +from openpype.client import ( + get_asset_by_id, + get_subset_by_id, + get_version_by_id, +) from openpype.pipeline import ( legacy_io, load, @@ -65,9 +70,16 @@ class AudioLoader(load.LoaderPlugin): ) # Set frame range. - version = legacy_io.find_one({"_id": representation["parent"]}) - subset = legacy_io.find_one({"_id": version["parent"]}) - asset = legacy_io.find_one({"_id": subset["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id( + project_name, representation["parent"], fields=["parent"] + ) + subset = get_subset_by_id( + project_name, version["parent"], fields=["parent"] + ) + asset = get_asset_by_id( + project_name, subset["parent"], fields=["parent"] + ) audio_node.sourceStart.set(1 - asset["data"]["frameStart"]) audio_node.sourceEnd.set(asset["data"]["frameEnd"]) diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py index 5e44917f28..b267921bdc 100644 --- a/openpype/hosts/maya/plugins/load/load_image_plane.py +++ b/openpype/hosts/maya/plugins/load/load_image_plane.py @@ -1,5 +1,10 @@ from Qt import QtWidgets, QtCore +from openpype.client import ( + get_asset_by_id, + get_subset_by_id, + get_version_by_id, +) from openpype.pipeline import ( legacy_io, load, @@ -216,9 +221,16 @@ class ImagePlaneLoader(load.LoaderPlugin): ) # Set frame range. - version = legacy_io.find_one({"_id": representation["parent"]}) - subset = legacy_io.find_one({"_id": version["parent"]}) - asset = legacy_io.find_one({"_id": subset["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id( + project_name, representation["parent"], fields=["parent"] + ) + subset = get_subset_by_id( + project_name, version["parent"], fields=["parent"] + ) + asset = get_asset_by_id( + project_name, subset["parent"], fields=["parent"] + ) start_frame = asset["data"]["frameStart"] end_frame = asset["data"]["frameEnd"] image_plane_shape.frameOffset.set(1 - start_frame) diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index ae3a683241..7392adc4dd 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -5,6 +5,7 @@ from collections import defaultdict from Qt import QtWidgets +from openpype.client import get_representation_by_name from openpype.pipeline import ( legacy_io, get_representation_path, @@ -75,11 +76,10 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): shader_nodes = cmds.ls(members, type='shadingEngine') nodes = set(self._get_nodes_with_shader(shader_nodes)) - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": representation['parent'], - "name": "json" - }) + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, "json", representation["parent"] + ) # Load relationships shader_relation = get_representation_path(json_representation) diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index 22d56139f6..e3d6166d3a 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -7,10 +7,9 @@ loader will use them instead of native vray vrmesh format. """ import os -from bson.objectid import ObjectId - import maya.cmds as cmds +from openpype.client import get_representation_by_name from openpype.api import get_project_settings from openpype.pipeline import ( legacy_io, @@ -185,12 +184,8 @@ class VRayProxyLoader(load.LoaderPlugin): """ self.log.debug( "Looking for abc in published representations of this version.") - abc_rep = legacy_io.find_one({ - "type": "representation", - "parent": ObjectId(version_id), - "name": "abc" - }) - + project_name = legacy_io.active_project() + abc_rep = get_representation_by_name(project_name, "abc", version_id) if abc_rep: self.log.debug("Found, we'll link alembic to vray proxy.") file_name = get_representation_path(abc_rep) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index e9e0d74c03..15b89ad53c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -3,6 +3,7 @@ import pymel.core as pm import pyblish.api +from openpype.client import get_subset_by_name from openpype.pipeline import legacy_io @@ -78,11 +79,15 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug('isntance data {}'.format(instance.data)) else: legacy_subset_name = task + 'Review' - asset_doc_id = instance.context.data['assetEntity']["_id"] - subsets = legacy_io.find({"type": "subset", - "name": legacy_subset_name, - "parent": asset_doc_id}).distinct("_id") - if len(list(subsets)) > 0: + asset_doc = instance.context.data['assetEntity'] + project_name = legacy_io.active_project() + subset_doc = get_subset_by_name( + project_name, + legacy_subset_name, + asset_doc["_id"], + fields=["_id"] + ) + if subset_doc: self.log.debug("Existing subsets found, keep legacy name.") instance.data['subset'] = legacy_subset_name diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 068d6b38a1..632b531668 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api +from openpype.client import get_assets from openpype.pipeline import legacy_io import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -42,8 +43,12 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): nodes=instance[:]) # check ids against database ids - db_asset_ids = legacy_io.find({"type": "asset"}).distinct("_id") - db_asset_ids = set(str(i) for i in db_asset_ids) + project_name = legacy_io.active_project() + asset_docs = get_assets(project_name, fields=["_id"]) + db_asset_ids = { + str(asset_doc["_id"]) + for asset_doc in asset_docs + } # Get all asset IDs for node in id_required_nodes: diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py index 38407e4176..c8bac6e569 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,7 +1,6 @@ import pyblish.api import openpype.api -from openpype.pipeline import legacy_io import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -36,15 +35,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): """Return the member nodes that are invalid""" invalid = list() - asset = instance.data['asset'] - asset_data = legacy_io.find_one( - { - "name": asset, - "type": "asset" - }, - projection={"_id": True} - ) - asset_id = str(asset_data['_id']) + asset_id = str(instance.data['assetEntity']["_id"]) # We do want to check the referenced nodes as we it might be # part of the end product diff --git a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index e65150eb0f..6b6fb03eec 100644 --- a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -1,8 +1,8 @@ import pyblish.api +from openpype.client import get_subset_by_name import openpype.hosts.maya.api.action from openpype.pipeline import legacy_io -import openpype.api class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): @@ -33,26 +33,23 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): raise RuntimeError("Found unregistered subsets: {}".format(invalid)) def get_invalid(self, instance): - invalid = [] - asset_name = instance.data["asset"] + project_name = legacy_io.active_project() + asset_doc = instance.data["assetEntity"] render_passses = instance.data.get("renderPasses", []) for render_pass in render_passses: - is_valid = self.validate_subset_registered(asset_name, render_pass) + is_valid = self.validate_subset_registered( + project_name, asset_doc, render_pass + ) if not is_valid: invalid.append(render_pass) return invalid - def validate_subset_registered(self, asset_name, subset_name): + def validate_subset_registered(self, project_name, asset_doc, subset_name): """Check if subset is registered in the database under the asset""" - asset = legacy_io.find_one({"type": "asset", "name": asset_name}) - is_valid = legacy_io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset["_id"] - }) - - return is_valid + return get_subset_by_name( + project_name, subset_name, asset_doc["_id"], fields=["_id"] + ) From 240c7113601141dffe08233e3be5a9b9f5944447 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:53:32 +0200 Subject: [PATCH 252/258] remove unused import --- openpype/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6863edf3e0..edca1f45b8 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -20,7 +20,6 @@ from openpype.client import ( get_project, get_asset_by_name, get_subsets, - get_subset_by_name, get_last_versions, get_representation_by_name ) From d2bfcb80cb1597c35f6bbce1e7a4c51a909533b4 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 22 Jun 2022 03:53:06 +0000 Subject: [PATCH 253/258] [Automated] Bump version --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++--------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48d0d8181e..f0a9a9651d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,42 @@ # Changelog +## [3.11.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) + +### 📖 Documentation + +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) +- Feature/multiverse [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) + +**🚀 Enhancements** + +- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) +- TVPaint: Extractor use mark in/out range to render [\#3308](https://github.com/pypeclub/OpenPype/pull/3308) +- Maya: Allow more data to be published along camera 🎥 [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) + +**🐛 Bug fixes** + +- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) +- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) + +**🔀 Refactored code** + +- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) +- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) +- TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) +- Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) +- Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) + +**Merged pull requests:** + +- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) + ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1) **🆕 New features** @@ -26,9 +60,9 @@ - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) - deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) **🔀 Refactored code** @@ -61,10 +95,10 @@ **🐛 Bug fixes** -- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -78,8 +112,6 @@ - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) -- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) -- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) **🔀 Refactored code** @@ -90,7 +122,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: 21.1 fix [\#3248](https://github.com/pypeclub/OpenPype/pull/3248) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -105,9 +136,6 @@ - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) -- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) -- Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) -- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 7bf368108a..79e3b445f9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.1" +__version__ = "3.11.2-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index ae89e7d9d8..4b297fe042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.1" # OpenPype +version = "3.11.2-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f39a95422dd4c694743c458e285315c83e5121ef Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 22 Jun 2022 11:06:44 +0200 Subject: [PATCH 254/258] Update integrations section on the web --- website/src/pages/index.js | 45 +++++++++++++++++-------------- website/static/img/app_hibob.png | Bin 0 -> 15943 bytes 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 website/static/img/app_hibob.png diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 115102ed04..0886706015 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -361,7 +361,7 @@ function Home() { - DaVinci Resolve (Beta) + Resolve (Beta) @@ -374,6 +374,16 @@ function Home() { Ftrack + + + Shotgrid (Beta) + + + + + Kitsu (Beta) + + Clockify @@ -384,12 +394,7 @@ function Home() { Deadline - - - Muster - - - + Royal Render @@ -399,30 +404,30 @@ function Home() { Slack - - -

In development by us or OpenPype community.

- - +

Planned or in development by us and OpenPype community.

+ + diff --git a/website/static/img/app_hibob.png b/website/static/img/app_hibob.png new file mode 100644 index 0000000000000000000000000000000000000000..91dd8d3f6bae8ae50f7a74e0d3d2d8fba8b66b50 GIT binary patch literal 15943 zcmXwAWmr_-*S*6GF!TVDf^-QI(%oGO5+cITDInbpA>AQel8SVf3?&E%2-1yoBQi9+ z^ZP%~`(f^Unfu&x&+N1I+H0>J^+HRT0GApU000726$KsiH}by^6bpUTS#-n(0D3@G zLH3pR{9&h04vk)`@O{AO=PyTjyK~P|KGd+mK0-Uhxay#^`m*|szw&1=iCH6kaX#Hw z`3BgXbUGQFEOQ$CZ0U@~cpi<*2n`<5Yf6kABnd;9O(t3vBNilzORy?>vN>jTEz@*-Y{mr96`lg>72nh)!g9%YEH9V>9-l~QMs+mXcR9MDiE zf7j_ZbaZIUi2MkB!IY6LTkh59q&H%QHT>%_FLYcXRY>I@l|m>!KIiwu3Eyw z3Qtz9<%pz@qF40}VmxCD2C+}8IAH$Fd0}*ssZb5f4vl0-^_}5gRBL*)n-o=XH%7?( zt729tAa2jYc|{tT1;jqtXX0b3Gkh0qf2#fr{{Pix~7Vdt+roI0}%7=6w7#orM(?>F7%*Uo#;h=BwOi+W{( zx<;om{7Dv&lFQfySc*YKq7JePB1ohvsGtKTkPH(i4yf1(}cS zXJ!IFDal}_$YN$y5J^oK0E%oUg#LRTOh<~8KR99fx&N)*W5hyCY^3TwFkn}t;w35K z3isOfRjilu`u5vSh1b(hdGo7-bu~FI@NmE!n~a4tw$r$IrOd!ayRg9A?Pv_5qeI(ub*GPasy9*$MZt2!js z8wTpBacB>*4TT)lsXVZHAD3ILs*C`LA0hWyz$?BJWp2YWA%pdNfs?!f0YYW~XA?PX zL|U*){CooY;plzXMb}nPM8J5< z_w4d-K`JK_nV1re<%od~FgnxgF`OLSa!JO|16vn`8N;P z6hRLw2=;c}`LrQsCQu+0hqfXvwu1lfk>HK(GpQqzYQo1`pThR(KO2wrgP1`fc#vXy ztOtk;+?pM$_oPy8(Su&~cAoLQ?ovRr#2!ahIRwbU9wF8fK9P;=vsC&+Wa>J_sI)_k z`R&Uud@f3N$Jrr|A2)gCJ;!gr50=}45^Ynqd|>o>vi+8w@oo7M>Gfn7YnXWF)PYS9 z#Ey9>E;bVTeH%|~FI@4Cn#Ef)l-N*YW4@+wAErW5zsd-Fc3i!n`Dh**bjw7GHA2ph zD0Y>VNw@q~yB2&bi480+n|-qR{6*=yVGjcLM`U+wv<0cjFTsQ`h9edQXewz<7t}Yv z6IZ2IXD!V{BtNO6e~^ZS?YAWFkTd9*+O-@HbW+8) z`1V|EGTM8wbDu=k61>0&vde3iR@TQ zW}6|0yAfwhq^)3RWIJjSL~bxud2>Vx)JT!=J@pW5WyGWx&Y%vwC^O(VA?gWlbToL{ z9iq}bdCdZvFzH8ah+3lvg&hq`d#SNp0wzNpoFRyxzseiyDu%0YkX*)jYhFRuzOep` z;4oK5jO;n%YdQON>V*{P%Yi&EAfr+r##D9Vl6bBxzb|Hbfw5tzc=)1%5;VB6t!q<8 z+I5oEKLnG;%kJj)(`BMZen@Y7@tl5YF;6sf1x)VKV3$J0t2=)v45hR4haXC2rv6iV ztyl#G4KlxZX^^h=?WRpv_v`Uj{>uJ4&j|)=3%N>DaFA7tq;3uykhKOW768l1*acXi zmt9@W{IUE11Np0b#5S~BecC@nbx}@1rL9U6UcsJKg>{b+9CR(xuifNE#kKGi7AG?5 zoN{*?Tq2npr51@d^b~0AcDha8zh|<|nmK4n0nQeU^QbS?w^1XmBMRe2@{LBa{DnE& z?3LV(_kV~?IGS15vjNMHB?HEEOo|4s9s3%L?PHAu_I1Z~Bh}zn7Jkv#_k7&MvrefW z)25yBaoWn?zujiqH0FVA0uNiZBcDdKY<7~h8=4v#By?GQa<-d$KJ+^flE{69-wdho{!B&^zCPF~T7II%4nCEZUuLMm)TI2nc&`bdIvL>U*s?W< zejwHnVly5n{k)iGUL%PwLo%DVI^z{8Jiss}1^gYnlZ`uHwWQwLeD{LL8Izj1FDa%& z@?{nE+vxIINz7Qqx|`%QtQdWM1=WEoxwGrzpvG56ur%D;-t@1ca>4h^VUC(l7Z}2S zlt4)M;c7J%g~Cn|Q}1#2xD7EK=0Gq4vqU>UO?H6P<-V3S$|HgC{P0E=TQHb9L}n<{ zU9##sYU6=0sz8wc8E(ZEz^zEJVM@ElN%Eklai2f_U zzlkSY(lmdP(Axlm!1P!qV583tlU#s&nlUBj$w9YXJ83Ut@J8^F76{ba z@`a@8`+A&RH|F-6u(4+WmgKFM>w-T+6|{}Q-|r+^EcDfs`tvJ(4wi>cuPze{1Rc9U zpSXoxxK*-Y3DjS>F}5st796)Z%Q^33H~t1SWwGcsc-2d~QPyM7_f9|%w!QKWL9aC! z*wtVozsl1GR+h~OaoeOVjUos|o1VSEIsqeC3WX13dJPPiyhn)Pm7O;@Nto9e2@h`i zxZfxGZKDHdBE|EN&@b_b=6R&Fm^F?JOJhuf{@c4GPR8GpyN)JQjc6DZAs@EBCZKx} zq})j*QaGlid5C%xcL;*<7MScQuBMcIx^E!$50#m`IRjMGYNnwyP56a^6!I4b0GB9k z74?E{hS#893yTiFxB^7+cA-$W%DC3mE&b;qt+gT^01k6H&XZ3!zKQeYygXXk4P(kI zD;2~0iL&HoK?s7|9m7Uryo~~$H``Ft)jCZ@6@*fE3Wlh8`u)Z69Q#1A?p9`Kx~H>Pcr3}$iG_hD(!;+?K$_{IYMQq?xWGevTXz=Olml(*T4B6aVEi@!#r9JYZX2ukG zQ(*5zYEb?z)(+GDD|Y797?t1@xP6iD{5#%OR%&;Nd1~HX4xYRs#4SB{;$vbyZKUz( zxBrMwVaR$1Ci3j2+90ooOd99$Gze#=Hq-2*k%PwNbN*CosaPHxldz1-pHs7W<_VdR zNT#6=z1PZf1+&f@FZDq?yF0wlK6wgLzqg%sZ)cs^ayh(Q)5P+-3OH%Wu z15H?MhT*^7roJvi%yhG^

&5;JSVhK8`kJ{NAUH1MOkXs5w;xgZd~+qvCz^R>sV1 z9tj5+Wpt4tAO9ze;4JLh;J`gC0E_xj2Sw)oDwk*~n_NQ?>JmSUlVBXYvwBpLBf`y^ zSURD6wc7O^HS(ACJ?cS$*HfJ1V~7+#to3$eB*ftdCjESSG5~jZxULq;3U2OuJMi83 z;*?zR%<;GAX6g0Fs3R18>h|5Kny1f zc55uwRA#B1dAr?-ILzGb$Irl-Y0SoVc-{3bkQR83SEEP)U=3ExP^GDzJWGadqz#jQ zxci&7g^Hb&6>KVFb{exRgTRkmletv=%~yXXl5m>ldRs19XzlW^0Xtf7&Y zUrG_--q6$=PBmTZ#HEIxh~p2E34TcEVlPZmk4%p_{L$>MS}XHkjwHO-=*a;_0@?Z@ z*KpH~zq;iW>OTcE&8^AlNiR*jf2Nr+oiq{8ydN0POB+PFP<5l16isx0dL&w)!GO$i z1_1Yg-x?FlrDO>4qL-?V4Z{XP2y(=>b zWB|pY2$`HgkxdOyfYQa=qtlKyS*I$75w*I+DHs%Y$k8Xx3`+$H4DaxO7usd~xnvnS zX#670Lv@LbtOjXuH1Ciaq=B5#zsz0WKhKC`7?@@-%WK(BB{E%45+)Y)!dvCxxe(v7 zBCuXvEq0{^l;RFQ*GiH>17R`>Uj-=@*2RDVyEfhE-3&T3I%e#qsBU7wUnV(fiz2l* z!4u1as1eJNz(PffS&s&ez8?W>L6^oz^@aRS%#+OgLivjT(@wi5A6;k#?W%%U+9lg4 zC%Uh&6VNZqi|ZJ&O;Ss)0B5+T0?;L-R@-|ejh?43O7%XQe;b3zSSEgOOg{8@9db4!yPVm;i!vL&Rzz^`6xD6=`N26u|h>vr-Bm|C%}YLLf^&rJ@lWixI7>au6m80IoTi_qgRqt=T)+?-6WP}H4--a*SMwK5QXN+a zjrt2>2!x z^c5Y){>?I4D2Qql{PD`a{FMSAn3DLqPcnimQP+ef?muy&T^1wvPcl|M2M1YSSYxRG z40Jmj*c}1TTUQ!N7r^#~1588&-@Y=m91kd6Dc82EDMA!7d*47WTSXA57t}^vAcB&;gljZIP2r&^p>D1 z6j$^Yx%{rxEa~)L3~Kh6n%*-_sCXW(!(EL28q zKGLtIvbr+n^IaEdhEJ}snBSemv?F=qWOBUdL-Iu_rWfMF8V_U!o5+a{d~ge%P=_zH zKi;Ki?pgjvlOrvG*?B}TPOMpQmn|X8vFU5I=$7r1s(zNvx|;j16+f~;pN8Z+!*k1g z+|~P?X>t(!D*#^*!jZXHXxFA^ARH@|+IZO7^l3M1J{e%D%_6*L8b9ZxCfWoYi za-|MQ`a>+T4jaV$sf)|1&ELm&*M3*}XJ|&)eW!)B-WG#LAQ7=-Cm`UKANC;Iob{^CRp8N`E{r~t6ulCtJyCG&IMtYDZdKLuLldT`zy$sqPfQg-ZXJ8+QbHkp@Br=tW|8#FD{_*XN zb=n&!r8+rdhb2Q#g_hj)UxOsW3-$?z2jK2ND6;+nTzn#Cti=l5H1~_%L1~=Gt2tm2 zNbSdw7BTGYsXaHxi1fjL$!mQ5ix-C?&Rw(gwVlFPmP~*Hyv+ZOO+!V#5v++w8*Qu z>}l>$crm)WJ*w=wq^ACK{1-Wv^X{8+=9XWH2*hV>Sb`4Gx*}z3atCUV^5@5(lcv4C z2m%u^OElZE{s<=fxWMn`sCjJxsv#B9klEn}0iB%?%W2T9SiahhE;YgMZ#OPi@u2Qw zzX=AH|(y&IY8R7C+>OUPv*$(plAp(@Mz7cxp`7fyb{)vWY;l$?mP_uG<-1O@gdCG)%9SYZ}8tXZSJewLCX&41a5=z5e5@)u_H zzI~8eI5d@CJ(2I=vzjmci1#lAlQ5I-ioEcs*y5Mxz5(&=MNC0ZbH8Z@|3w4V6y1r| zDIPQFJSNzADPT|e;^LKz1|>es!{hUzO;BlGl9beE8X(b_V3lX{ySFuj&@}JQpBPC$ zEQ9%^_i-5v6ap6AHdP9@HSCT3{MujLVILS)Z05-WOmnU zCDTCn2ebr?ypulwM0g1oBWF`mHCH^*V{)jA!)m@9wA?6BI!bVyIyjAU+;jgSSmHg^ zzwg4qrQ?sf-=Eh=P7=iqnh{{p5qbMKW8B@%mqo`{wo0Deo>YQ3F)i{HZSTf-kR2Pg zJIW6r0SFr`WY65sn!d)86BFR{eBw1nlAy4^rxTk2U!_#u*Ps*(LTU>x9`mv2grtQ# z+R5z;OX2bR;{OYO_e4IGEX3Z*uT)Gx^eY2G(He-b3Anq*yS}hkrT6>vZ``p8+YR&r8AF7Lw|zNY|k%|uGfP9q~! zFK%^vNcY~zW|E~YR!7K&eVhN@SSw4hHViOgy8PHXetENBFw=Q(C}Xi99bskWR~p!a zQ6Ru$iNM#!wQbG&+NU5GN@q+pB%MP1v~R~HHJq9{T&uJ|nPfGI*^ndAP6jOm$}vP< zi$#8~8CbU5F*w{Wm*eTVx**DL$3+>K%Iuwa$)oWXrcLqe+tVit{3Wf7C4!N#??2Kb z2+;h*lb>$2Zkb83xUJh12YK-pM>K}%wv0lm>MX_M1zNaKM z%Avt^zklYM_JY^{87?sT*qJeK=1%2qCSbKvbvz1>espPUK^4n;fjkW*@U_>Xu{O2f z1A0kH$2LuPUcJMVChZ_5w&R%bDGDZkCrDk1-Pgt+Gq6jG>YxD}m4My1Di7Pfkr6o~4am3dXU&}-~@P)`)tM2mm7cQNZRT9(v-;vSF2 zgdNY*{iggUlV1~@Bmi&E15O~XGpqihQ1P?N#|~J}>=7c)qHtoR``v}v^X@UG(=s;Y z%DCwdq~;jZQvNvXstBFDaeb-<{<#u-Yh4(IW*tIMb-z@A{TH^(yI7XPz5To z;0d4E{uX@c=Ztl@0=&NmJsD4FD%_(pWRwj_bYw7(eY36sxRDV`>vE3&v(UZZ?dY9 zOUwJ4*N{-#|yZO z5UTkpG20-Bln>#*hl3OSNE@7pE?cX@>iOAG7kq5NFrY;}7Vhr7+)-mEW31 zp|**IixECqAa7JCe5~-0eh>C#-o5(k1C zr_(^WDnjII#>K1jsqd)NN4F-LPjXevSHB-)G=alH`#}#HU~N_83*7a}8{}0t7JlFZ z>K8&N-{%*#B{eknhVW#?^E%3PsvD)@n=BhmMGeJ!Qz7K#Prl=irXs(DXY)U=i64JB z>t$mgd@J&iji+>hf2uJA3095tqK)2Yl$KjhLifkKR?_fjJRp5ZH=xs0gNKHmxjc?B z(m~ALsWv@cx4~gBW0uz{j}pxRb|;z)nr{>}%jGoFNF;!nU~Nln$q+ z99qEzcrRW<{(Nc<7Ct~N6%5(&_%N@sSL>AUv@##{C)jaNArETIr~I zmfX)V`BojE!LDqY{jKuN-{HkUw3C)f7>-zmk}pmoacTgw`WY zXEAVP!^u=7Z!jiI1EKC4%vIX0*}BkbwqOw}Q|6g_@}toui#^ECdqMAuM#48(a-kGw z&@Z-4HcGP$GOpoQa%BB zP+&X*Q7lUg!>A-6A&^C5(2A~SM`NfL_{9OShgrg#nS@Zncc{M}YClmU|IATg2>k{I zkK=sg=YPLhh;3ld;ZiJ0=eL9%eCs7|Ux_X7FBw1g<9K!UboE)CV{yJ8v2U*@{UgeS zxRME9S7Jmeo>&de(M5j9-QLP}e0z?F4EF+22mhK!+}0=h7VntqKS$~cYOt3&-Xx=E z35Ohs0=zb)eFtUtxD8>?U+Y~F1N}!oN(A84@IEu?2c10KRmtI5FC39K;_tYrT3D?7 zSj7iIpctu-N)y}hNs5}QmC)tnaU*X8HBI>0_OpUsYX6Ifv){{#7(t%hlQv=VS#asI zPhD$VU7|_%N;zN5{k+HgZNZQf+vdcAmJvh5f4RTSn1UN-5C9&3d{1bP3ySAn&cM?v z|2X(~Th^>)tP7WxF0&qP-KQiJCR={-+4Kuxlg$`U@5F))Ed@YC%?zNsnZ70UlKuD} z?{3PMmsV$#JJQBKv-hj*eJ>YecMNVm_($IVmxc~Aw4gam5>EvaBM4_U@06{0-~k^O zE1cQZ{?0Vck%&Ty5d~~$yw&QM5Ub$_mqMf^0m`f#wl>q-_So#;eMzZ z%RUT9zXX?YrI#8BJgb3Q?Yve}Q7vNn2lH)@d5Yc&ch>^{FHuHbN0<50eCk9DE2`2^ z0C=#zM|Ji1y zMqB%_zLLY2VQmPpz7YIn+;yf2EIcEj53fQ?Ty?tmP0TGp-oGDyTF7d0J7Oxhq+)C^ zc8<%THb@}Mw2^6e4B6BJykc%iK&~2ESOtHi5&k%2?pNx%j!!bax(4)l%D#Qu1ejc8 zMd)!Z@c(Gw3&kMuy@0ukNnRYU{!wgdlIgHm`^_RZw2RXC$HjqcCrRW)eXzwHdks8K zn2uuh_F8;{XEsV#+>Z|R+t0EP^MAsxyO%Wx`&L+&C|~F~j+JkXO)nBru67a05fN_x zEF1jDN;$xd-NgsVa2?A|i#g03>3I$e16_+me|F?$!;>!73+9)IkUxk&MGqck#*vt- z0;B_{I|=J15tOUd_^4^@@67#rcnc{vOFr6w1V3a-nAhuJW-Od}5^>eU)Lo5#<8iCL zbGDt*k3$h$L^$lQ%LlbL&%|&hKNLwE5l{7COthUg(_MW8$W8an)ui>KG~AOlNnVU}#;bL$zD9f` zCpym(ekqk=f{tXhc{wnM^mZONc=qOOk~Po@bM80dI-!f=cGf|rQ}lhngHD}fie0Mg zl z)Q&upx4r&bMo--vxSBn%@b8)O-vn)pa=Ld?CUcHsVQkDQr48Z=^gtx~gKl++30C#WMrlYfmv^}ri zgE*seLqJ!~s|)e{=&{7i0MDA#c5id?QMP!}{SSE<8&75j)J4%D5P<8kF+=~o+W#)T zeBxb~fwHz)v+hDhTuvsYctVo+K0kgH3<~_WHsfg(XpJD~M)8 zZo$$OOzUI>&|&^LDe|8oJs|8}X$csfL_7qV!0t-4vJf+Zb-uHCyA9Am;+Z1 zUvB!m3I64q{Ve~IckkPSYKzxHzj`WOrdtBP^>u(?XjaJT8uLGSo}%4gXGZ_!-4E{> z0`?2BBa&JUrN)1$OI$pqbFFZ99GjiluoBIFzlkuE+~BzTkVA%`H1`c)w)Pg4^B!b= znTy_HYH>Sxnl8vL&mCyD>)WdgZ>Gr)AD}-CI_Tmq7NK{mE8RhKC-a}I^0P;s+fMF12*yaa0``CGlLnT7+&*S(hCz=Rn%A@}vvqtf8Yv7pC)TdZDO zeB0quSVP)s21sIaX0^tHAe!_vzy({u@k%T*eN=WqQkdy?;BWTvzf0GGzqPa_RGTv87ip3W}zAHN|wzbTVd ze;i!Q|DIZLvsRMy?o-kPwSqej0C$KPeB@4bmL}Bbl`(*F7Yo=nxjXg3j1oFI>G2;g&^*@9k%ACt$bb2!_c_KF`FQb9q^!k zvg#5~On$Gt?N8a@2#TE~{BXW|LGOLbS3@XC`3OH2NBd*mgu^{|SNUF?gr(!#%ao6)192_!;wCSmhft5+ z>9EamIzUgZX$?<)3lod{9e*i9_BR75`0JFoMclJ{X&KLJfpDkL%w-5G6+;8eNQtnu z3#mOOp^RC8bJ-7vNcoM$7LZLi{Ofu+Ii~wQ-u?}Ek5Yq+yftdV{s0czDkH~XJo!=i z_}Dv^i8!>aeMuD)4bh=(7Dq>3CLZrXiCbF7v_$DMiX63{SmeYXa(vX!o`-J%f0kJoR(iP!0- z<~yS+ar6D=@pIVq+g-Zx`eRpqDE}lGX(_k?^_}noa6~&6Liq07kWRKAJLsF^*hX2J zY7dS6sFU?6Cm`dcpv?(Dc#r0`o{>7-cd2e7+-4xS&~edi)FX`B)^j&y$4ECK+1r4J z;H2|}xI%&L^FrkrS*$>JY@ON#hEY){rB)i&t&iRP=i^GMHWNzNs_Cawig%XHIVXp+3r?@U!dc)y($PeS7Sq5^iGTvOh?{(U0NRH(()o5F9SA;2>XHoQ zF6a)*#3U|lJkx*Hy&5&c)VlWhrIPJ899gXbY4`18*aOmlepHM)vg|@*jy;X!DeUsj zMMqeL6vI7e;k&7$C2!+}dmBGqNWDI?y`Z)BI6lnP^lM6Kp8}4no0;DbG116I&08d_ zU~+!^0mZ|3IzUh))jEBKagzhS@6Nv`={;_Lvi>#g$xQw*p1zCIn0ta%`1lBK!2=%P zJ8$qfrSWgZD*y_Q@5A5QtHWtwVa7)kR>Y$@Z^hQ}lTZc*trcuATC3~zTFSSIF#P;f zNh&lA0|%s4^2JMW&lzy%PIUU`k3>=#oe=wB}*D99L@uhBnu@`nNxKk9w|#01)Y6OS3oqln z921tkI0%=pFgPT_DR>@l}N*YIdEWbJ^KY(Sk*#tePd~H3)S;F`Ck`+Jb zZs0}N0{;6g29uT5-(2LafA!?paV|l~cZsEFkL+EKW^;btXJaF@VUUlhebi)$H1Yd_ zlJNkGb!eG6I-pLr9ESK5Z*(~j1(<=g6dfLr!UtdJ9*SJdGep3^^}?Lq(uD2J%k+1z z0Nt@~h|70gOm~g#G6OV#(JT;Kd~rZ#__kFtCC{1{`v-mZZ9JxAvvl$gyKJXN{DF#@ zk5Rde*BuCfBU+s%*6(4NZ*#nM4;bxo-^Z|XR?nvQrhFmPHMR^6KQq_e2z|mJ2EuI#2KItX z57vw-wI-XU)iEH0D$-AsfBgUqlqB8nfmKW&)CXe{WLFnSiM{vL46zT1=H}N~ntxjR zQzdTSs)}YUFSX*KR!kJ_drjdFdoOng-pJfM3l_0>Ez}5}e|9LOluC08ml0M|j~781 zWC`Ef@1xDQDQ&FVciWC2RDt4{(OuU*!G5@u_$I9TH~Sw(3N`3GPGkMUaUzZiMwcig zaNc2%LCX~DteDA+rj=&;FPMR%Nlr*uE^J>`N9(PnnqAGl9Kk^0HTK%KR?^Yo>K-3! zyv6hE!h9dF3~arnzX0=mwPC+iJZyc$z^X>lBGxpQ7hk!3jN5VcRA}$WlZsC^a(5jE zQT;Hj%=n#e%PdIfgT&J#G2LU@D)J_q(lVcD87c^*I9>brDV!ZM(2jDE4jWm}#5#Ve zPLDE${+h=BH37B94-Fw#!L9idqAT5x)~)JFsny9YV$Rbd5d7;7k76G@mhjadfynC1>q6^4VW^TAbQ8E zDdrB`@fY{jRJmv5!!OI5`W&^ja_pL^K%7+v#|`JV>!g30#mMyl*AMWE*A>q8yd&Dj zbtyu@Uy~7CBxrkX&Y##1gSa0~?zJ|~Mj%7~p3W>+)$qZ`oT9i2Kb6CpY-ntA>2W5l zmfObp5=w2HXfrGK`S;N@>DT5A>D%#vhl!c>9NLf5ZC32GF@;gEyIDrto##)zf7{vct#v_F6q)A$_A6NkrTQB6$l2gGTC0{g;TK2a z?7K>#hV#W!l(CL=)#XodpOVh9Nh{t)iCLe?NQadrb1V(41>vRNkoES42@TP{xdP8L8l4N0{qOxN1jaCL zjag3&F14zD1j+LAmzz)~hHyLk|D|{%R2^&NW)^_U4FEDreCZ3~8hiI$;BaIKw}a-O zUERy7)Gal1w;fu%xBNp8(+|saegvW7aQAaM&g<4ZfZ6=(;-VD=q@8^==`D-Y*_UDY z@LRL|l<7VRAC3n5&8uO|`!AM$TUA$4bK35BhB%w|g_YYVFY=yao)*l(Cz82G*D52n zuP-|3Sd1^S@qQM-!^PL&7vxQ4FJ$IpX7fgPY>`fh;c4Te0Uuc@)U3iqjub3W-z9;3=ZSujo$nBLOVZ0k9jiD zb5UsL(0>eJ*WFSrFI(qGJuckJu12$eLLcpa8k);HP{ov)#sQP}mEid~y%|IMLkmNe zKVfyJVD|su7bjGCF3lbFF`j(}ZOJW}xNSrxBtpQo+1N~(n_#t_p-obPSLHG7P* z^wuAc({sL+%pKiId&${Qb>`8uRBt>T;4x7{>0nL{I*t4WLW3IQN%Gi#yx?Y8w%9BD5SjYfiw&S^dC3^8} zOa`>6jE&s%ahrEqL*yIIrE?4DAfC(n^g4C@U}GqU8A+^Q9m$+=HMEtt5QG-W6ru!z z<{Lq~1aP6qbklGpV=zWW*7-=3MeF`K9M zZ1bq&^S*?Sp!t*Jzm@hkvyv=pVi(*EA>;`{gO$NSd3BQ1HIGZ|K#MlKxzm5Q^B=Mn z#7A5aU5@1R;&h^$`&b5VPxBeS0&u+<{0f_v?jcqS{rK-y?#zhQ(qr{QXurb&8!;RMF{Ez+-?og|2;^HvR zz=a+rx`I4E5-Nk1oJ-DjbQlhpV`K}Hq755sdIovc@(-W1GGcCWQ{^ER6Yr;I^Uy*y z6k+Ze!aRaVe38|)IN`SNfTdIq8H($Sh5-*`uzB(SVtC!gxOR5x@^z=c=z)h0eGQ&- zs3zJhNYWHRX%wng-!M|F4+aGj2|IP%JpuZDm9x9++f3ofcEv7e!ftTN4~mO@hFA`$><^ zFMZ(zF@)vEFTO&7-4f{8-rwYzej3)?YQV%--&#sMPO3aq0RpT zsG5f^4OP>_cLoA)ia&&Tz6Gz7%g>qGd4H*x0Y5CNrOrU;$7T^cJLUUy?p0Gr!MF+I z|4l)8LK78W#%Oo##67+U)hb&fdN{?dyQR=@K; zL13}8I5ZRgVpF`&r$9U2NooHjF<49I_maI~Y8ABzRfJ*}W`Z`2YI!K`C z^cU@~Dd?>a15k7vjdNI0Z&jzP_09kDv#USRJ|3ZF+#2i@I5SfUCjFou!-^LceswZ% zEKX~gWM1lGv{6%zB=8;X;$q7hO8I%Qf|oN-<(KDPHp{q3%BFHJ7Avlv7sW+Cr%+@g zm$okk$pGG+c{q|aoR6CKxYLm+P6@;f5HreSeNZrN@5S&>XZ}_q+ItXNTG3Jtw|A>1 zLmb@1^+Vc}!B+9cy=ji4uuW z6?TfIX)1~+f8i(qYYYdOwt1$@ddbGlD}YphDdncts4!OLF!M>utiGXe=?(ZjnF(}| z83P$aRih>xK;|E`zO2;T6lpBGBYE`~D`jdfi+yO7AEU=MMd4)n5sh^sr@t8`zxLDs ziXF&$jh7Pkl@yB^r!VagcRU4SoqOWDbCzG=-{yl{|J6wetb24&Sr``CzA9)=cQtjA zaGfLBoOp^UWh$Ri(x<+!)*#-+G zOk&vZp4m~0JY|j*Q-vsV7B`0UidDS6YbVaAdw1*|5*RNLfk=17%07E6vDal2_!S3{ z$~?#zg@DlmXmM7C2XFN~hO~l4ko~gv!Zcrk(=ji){@hpRWR~t?1*}BS+*!%e8&k6( zBSivj5}%xx{FtHb`ns9F1_QY+W+#Iv$XKfhFY?gm?TAPAZ3L$!+L81WNQS&yE>hdu z86cAFFqzd@E=f#|)F7#er2hf-Gvb^be?Q@^2+ITg`h#(+km8b8Hqf4I@q{FbO0svM zywj#qoqjocB|sDS{syxT`s=5_vgQy?FI#{uF`iMOAl=$?x_2^M&98YVn9(D`kI*B+ zj4)yW{?zFgaCU23UgPQU117(mEed*-ztSht%=AB;VMO7FOFV_IFI0Of;EtHKZa{-* zRSpHY;DPtM+ZIXRmULK@ZJ)WsN=6Pk-gMc``GWnLR0X&;@hipdz5n#tvTMY}Kz@H} za3#c7vET@$STN#Y?I8i%0=oRr^Z4mXT(J@A3Egi`+M-lQ+26s=RmWi<`Dnh45UT2w;+0tjt%~wea*K9Oy0#9oj72Sm$*Hk_nBEK~JLA&L!+?q*% Date: Wed, 22 Jun 2022 13:49:54 +0200 Subject: [PATCH 255/258] Revert "Shotgrid: Add production beta of shotgrid integration" --- .gitignore | 3 - .../plugins/publish/submit_maya_deadline.py | 1 - openpype/modules/shotgrid/README.md | 19 -- openpype/modules/shotgrid/__init__.py | 5 - openpype/modules/shotgrid/lib/__init__.py | 0 openpype/modules/shotgrid/lib/const.py | 1 - openpype/modules/shotgrid/lib/credentials.py | 125 ----------- openpype/modules/shotgrid/lib/record.py | 20 -- openpype/modules/shotgrid/lib/settings.py | 18 -- .../publish/collect_shotgrid_entities.py | 100 --------- .../publish/collect_shotgrid_session.py | 123 ----------- .../publish/integrate_shotgrid_publish.py | 77 ------- .../publish/integrate_shotgrid_version.py | 92 -------- .../plugins/publish/validate_shotgrid_user.py | 38 ---- openpype/modules/shotgrid/server/README.md | 5 - openpype/modules/shotgrid/shotgrid_module.py | 58 ----- .../tests/shotgrid/lib/test_credentials.py | 34 --- .../shotgrid/tray/credential_dialog.py | 201 ------------------ .../modules/shotgrid/tray/shotgrid_tray.py | 75 ------- openpype/resources/app_icons/shotgrid.png | Bin 45744 -> 0 bytes .../defaults/project_settings/shotgrid.json | 22 -- .../defaults/system_settings/modules.json | 8 +- openpype/settings/entities/__init__.py | 2 - openpype/settings/entities/enum_entity.py | 114 ++++------ .../schemas/projects_schema/schema_main.json | 4 - .../schema_project_shotgrid.json | 98 --------- .../schemas/schema_representation_tags.json | 3 - .../schemas/system_schema/schema_modules.json | 54 ----- poetry.lock | 16 -- pyproject.toml | 1 - 30 files changed, 38 insertions(+), 1279 deletions(-) delete mode 100644 openpype/modules/shotgrid/README.md delete mode 100644 openpype/modules/shotgrid/__init__.py delete mode 100644 openpype/modules/shotgrid/lib/__init__.py delete mode 100644 openpype/modules/shotgrid/lib/const.py delete mode 100644 openpype/modules/shotgrid/lib/credentials.py delete mode 100644 openpype/modules/shotgrid/lib/record.py delete mode 100644 openpype/modules/shotgrid/lib/settings.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py delete mode 100644 openpype/modules/shotgrid/server/README.md delete mode 100644 openpype/modules/shotgrid/shotgrid_module.py delete mode 100644 openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py delete mode 100644 openpype/modules/shotgrid/tray/credential_dialog.py delete mode 100644 openpype/modules/shotgrid/tray/shotgrid_tray.py delete mode 100644 openpype/resources/app_icons/shotgrid.png delete mode 100644 openpype/settings/defaults/project_settings/shotgrid.json delete mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json diff --git a/.gitignore b/.gitignore index e18c94a1f4..28cfb4b1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,3 @@ website/.docusaurus .poetry/ .python-version -.editorconfig -.pre-commit-config.yaml -mypy.ini diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index dff80e62b9..9964e3c646 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -519,7 +519,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", - "OPENPYPE_SG_USER", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/shotgrid/README.md b/openpype/modules/shotgrid/README.md deleted file mode 100644 index cbee0e9bf4..0000000000 --- a/openpype/modules/shotgrid/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Shotgrid Module - -### Pre-requisites - -Install and launch a [shotgrid leecher](https://github.com/Ellipsanime/shotgrid-leecher) server - -### Quickstart - -The goal of this tutorial is to synchronize an already existing shotgrid project with OpenPype. - -- Activate the shotgrid module in the **system settings** and inform the shotgrid leecher server API url - -- Create a new OpenPype project with the **project manager** - -- Inform the shotgrid authentication infos (url, script name, api key) and the shotgrid project ID related to this OpenPype project in the **project settings** - -- Use the batch interface (Tray > shotgrid > Launch batch), select your project and click "batch" - -- You can now access your shotgrid entities within the **avalon launcher** and publish informations to shotgrid with **pyblish** diff --git a/openpype/modules/shotgrid/__init__.py b/openpype/modules/shotgrid/__init__.py deleted file mode 100644 index f1337a9492..0000000000 --- a/openpype/modules/shotgrid/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .shotgrid_module import ( - ShotgridModule, -) - -__all__ = ("ShotgridModule",) diff --git a/openpype/modules/shotgrid/lib/__init__.py b/openpype/modules/shotgrid/lib/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/modules/shotgrid/lib/const.py b/openpype/modules/shotgrid/lib/const.py deleted file mode 100644 index 2a34800fac..0000000000 --- a/openpype/modules/shotgrid/lib/const.py +++ /dev/null @@ -1 +0,0 @@ -MODULE_NAME = "shotgrid" diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py deleted file mode 100644 index 337c4f6ecb..0000000000 --- a/openpype/modules/shotgrid/lib/credentials.py +++ /dev/null @@ -1,125 +0,0 @@ - -from urllib.parse import urlparse - -import shotgun_api3 -from shotgun_api3.shotgun import AuthenticationFault - -from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry -from openpype.modules.shotgrid.lib.record import Credentials - - -def _get_shotgrid_secure_key(hostname, key): - """Secure item key for entered hostname.""" - return f"shotgrid/{hostname}/{key}" - - -def _get_secure_value_and_registry( - hostname, - name, -): - key = _get_shotgrid_secure_key(hostname, name) - registry = OpenPypeSecureRegistry(key) - return registry.get_item(name, None), registry - - -def get_shotgrid_hostname(shotgrid_url): - - if not shotgrid_url: - raise Exception("Shotgrid url cannot be a null") - valid_shotgrid_url = ( - f"//{shotgrid_url}" if "//" not in shotgrid_url else shotgrid_url - ) - return urlparse(valid_shotgrid_url).hostname - - -# Credentials storing function (using keyring) - - -def get_credentials(shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - if not hostname: - return None - login_value, _ = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - password_value, _ = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - return Credentials(login_value, password_value) - - -def save_credentials(login, password, shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - _, login_registry = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - _, password_registry = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - clear_credentials(shotgrid_url) - login_registry.set_item(Credentials.login_key_prefix(), login) - password_registry.set_item(Credentials.password_key_prefix(), password) - - -def clear_credentials(shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - login_value, login_registry = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - password_value, password_registry = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - - if login_value is not None: - login_registry.delete_item(Credentials.login_key_prefix()) - - if password_value is not None: - password_registry.delete_item(Credentials.password_key_prefix()) - - -# Login storing function (using json) - - -def get_local_login(): - reg = OpenPypeSettingsRegistry() - try: - return str(reg.get_item("shotgrid_login")) - except Exception: - return None - - -def save_local_login(login): - reg = OpenPypeSettingsRegistry() - reg.set_item("shotgrid_login", login) - - -def clear_local_login(): - reg = OpenPypeSettingsRegistry() - reg.delete_item("shotgrid_login") - - -def check_credentials( - login, - password, - shotgrid_url, -): - - if not shotgrid_url or not login or not password: - return False - try: - session = shotgun_api3.Shotgun( - shotgrid_url, - login=login, - password=password, - ) - session.preferences_read() - session.close() - except AuthenticationFault: - return False - return True diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py deleted file mode 100644 index f62f4855d5..0000000000 --- a/openpype/modules/shotgrid/lib/record.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Credentials: - login = None - password = None - - def __init__(self, login, password) -> None: - super().__init__() - self.login = login - self.password = password - - def is_empty(self): - return not (self.login and self.password) - - @staticmethod - def login_key_prefix(): - return "login" - - @staticmethod - def password_key_prefix(): - return "password" diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py deleted file mode 100644 index 924099f04b..0000000000 --- a/openpype/modules/shotgrid/lib/settings.py +++ /dev/null @@ -1,18 +0,0 @@ -from openpype.api import get_system_settings, get_project_settings -from openpype.modules.shotgrid.lib.const import MODULE_NAME - - -def get_shotgrid_project_settings(project): - return get_project_settings(project).get(MODULE_NAME, {}) - - -def get_shotgrid_settings(): - return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) - - -def get_shotgrid_servers(): - return get_shotgrid_settings().get("shotgrid_settings", {}) - - -def get_leecher_backend_url(): - return get_shotgrid_settings().get("leecher_backend_url") diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py deleted file mode 100644 index 0b03ac2e5d..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ /dev/null @@ -1,100 +0,0 @@ -import os - -import pyblish.api -from openpype.lib.mongo import OpenPypeMongoConnection - - -class CollectShotgridEntities(pyblish.api.ContextPlugin): - """Collect shotgrid entities according to the current context""" - - order = pyblish.api.CollectorOrder + 0.499 - label = "Shotgrid entities" - - def process(self, context): - - avalon_project = context.data.get("projectEntity") - avalon_asset = context.data.get("assetEntity") - avalon_task_name = os.getenv("AVALON_TASK") - - self.log.info(avalon_project) - self.log.info(avalon_asset) - - sg_project = _get_shotgrid_project(context) - sg_task = _get_shotgrid_task( - avalon_project, - avalon_asset, - avalon_task_name - ) - sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) - - if sg_project: - context.data["shotgridProject"] = sg_project - self.log.info( - "Collected correspondig shotgrid project : {}".format( - sg_project - ) - ) - - if sg_task: - context.data["shotgridTask"] = sg_task - self.log.info( - "Collected correspondig shotgrid task : {}".format(sg_task) - ) - - if sg_entity: - context.data["shotgridEntity"] = sg_entity - self.log.info( - "Collected correspondig shotgrid entity : {}".format(sg_entity) - ) - - def _find_existing_version(self, code, context): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["sg_task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["code", "is", code], - ] - - sg = context.data.get("shotgridSession") - return sg.find_one("Version", filters, []) - - -def _get_shotgrid_collection(project): - client = OpenPypeMongoConnection.get_mongo_client() - return client.get_database("shotgrid_openpype").get_collection(project) - - -def _get_shotgrid_project(context): - shotgrid_project_id = context.data["project_settings"].get( - "shotgrid_project_id") - if shotgrid_project_id: - return {"type": "Project", "id": shotgrid_project_id} - return {} - - -def _get_shotgrid_task(avalon_project, avalon_asset, avalon_task): - sg_col = _get_shotgrid_collection(avalon_project["name"]) - shotgrid_task_hierarchy_row = sg_col.find_one( - { - "type": "Task", - "_id": {"$regex": "^" + avalon_task + "_[0-9]*"}, - "parent": {"$regex": ".*," + avalon_asset["name"] + ","}, - } - ) - if shotgrid_task_hierarchy_row: - return {"type": "Task", "id": shotgrid_task_hierarchy_row["src_id"]} - return {} - - -def _get_shotgrid_entity(avalon_project, avalon_asset): - sg_col = _get_shotgrid_collection(avalon_project["name"]) - shotgrid_entity_hierarchy_row = sg_col.find_one( - {"_id": avalon_asset["name"]} - ) - if shotgrid_entity_hierarchy_row: - return { - "type": shotgrid_entity_hierarchy_row["type"], - "id": shotgrid_entity_hierarchy_row["src_id"], - } - return {} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py deleted file mode 100644 index 9d5d2271bf..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ /dev/null @@ -1,123 +0,0 @@ -import os - -import pyblish.api -import shotgun_api3 -from shotgun_api3.shotgun import AuthenticationFault - -from openpype.lib import OpenPypeSettingsRegistry -from openpype.modules.shotgrid.lib.settings import ( - get_shotgrid_servers, - get_shotgrid_project_settings, -) - - -class CollectShotgridSession(pyblish.api.ContextPlugin): - """Collect shotgrid session using user credentials""" - - order = pyblish.api.CollectorOrder - label = "Shotgrid user session" - - def process(self, context): - - certificate_path = os.getenv("SHOTGUN_API_CACERTS") - if certificate_path is None or not os.path.exists(certificate_path): - self.log.info( - "SHOTGUN_API_CACERTS does not contains a valid \ - path: {}".format( - certificate_path - ) - ) - certificate_path = get_shotgrid_certificate() - self.log.info("Get Certificate from shotgrid_api") - - if not os.path.exists(certificate_path): - self.log.error( - "Could not find certificate in shotgun_api3: \ - {}".format( - certificate_path - ) - ) - return - - set_shotgrid_certificate(certificate_path) - self.log.info("Set Certificate: {}".format(certificate_path)) - - avalon_project = os.getenv("AVALON_PROJECT") - - shotgrid_settings = get_shotgrid_project_settings(avalon_project) - self.log.info("shotgrid settings: {}".format(shotgrid_settings)) - shotgrid_servers_settings = get_shotgrid_servers() - self.log.info( - "shotgrid_servers_settings: {}".format(shotgrid_servers_settings) - ) - - shotgrid_server = shotgrid_settings.get("shotgrid_server", "") - if not shotgrid_server: - self.log.error( - "No Shotgrid server found, please choose a credential" - "in script name and script key in OpenPype settings" - ) - - shotgrid_server_setting = shotgrid_servers_settings.get( - shotgrid_server, {} - ) - shotgrid_url = shotgrid_server_setting.get("shotgrid_url", "") - - shotgrid_script_name = shotgrid_server_setting.get( - "shotgrid_script_name", "" - ) - shotgrid_script_key = shotgrid_server_setting.get( - "shotgrid_script_key", "" - ) - if not shotgrid_script_name and not shotgrid_script_key: - self.log.error( - "No Shotgrid api credential found, please enter " - "script name and script key in OpenPype settings" - ) - - login = get_login() or os.getenv("OPENPYPE_SG_USER") - - if not login: - self.log.error( - "No Shotgrid login found, please " - "login to shotgrid withing openpype Tray" - ) - - session = shotgun_api3.Shotgun( - base_url=shotgrid_url, - script_name=shotgrid_script_name, - api_key=shotgrid_script_key, - sudo_as_login=login, - ) - - try: - session.preferences_read() - except AuthenticationFault: - raise ValueError( - "Could not connect to shotgrid {} with user {}".format( - shotgrid_url, login - ) - ) - - self.log.info( - "Logged to shotgrid {} with user {}".format(shotgrid_url, login) - ) - context.data["shotgridSession"] = session - context.data["shotgridUser"] = login - - -def get_shotgrid_certificate(): - shotgun_api_path = os.path.dirname(shotgun_api3.__file__) - return os.path.join(shotgun_api_path, "lib", "certifi", "cacert.pem") - - -def set_shotgrid_certificate(certificate): - os.environ["SHOTGUN_API_CACERTS"] = certificate - - -def get_login(): - reg = OpenPypeSettingsRegistry() - try: - return str(reg.get_item("shotgrid_login")) - except Exception: - return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py deleted file mode 100644 index cfd2d10fd9..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ /dev/null @@ -1,77 +0,0 @@ -import os -import pyblish.api - - -class IntegrateShotgridPublish(pyblish.api.InstancePlugin): - """ - Create published Files from representations and add it to version. If - representation is tagged add shotgrid review, it will add it in - path to movie for a movie file or path to frame for an image sequence. - """ - - order = pyblish.api.IntegratorOrder + 0.499 - label = "Shotgrid Published Files" - - def process(self, instance): - - context = instance.context - - self.sg = context.data.get("shotgridSession") - - shotgrid_version = instance.data.get("shotgridVersion") - - for representation in instance.data.get("representations", []): - - local_path = representation.get("published_path") - code = os.path.basename(local_path) - - if representation.get("tags", []): - continue - - published_file = self._find_existing_publish( - code, context, shotgrid_version - ) - - published_file_data = { - "project": context.data.get("shotgridProject"), - "code": code, - "entity": context.data.get("shotgridEntity"), - "task": context.data.get("shotgridTask"), - "version": shotgrid_version, - "path": {"local_path": local_path}, - } - if not published_file: - published_file = self._create_published(published_file_data) - self.log.info( - "Create Shotgrid PublishedFile: {}".format(published_file) - ) - else: - self.sg.update( - published_file["type"], - published_file["id"], - published_file_data, - ) - self.log.info( - "Update Shotgrid PublishedFile: {}".format(published_file) - ) - - if instance.data["family"] == "image": - self.sg.upload_thumbnail( - published_file["type"], published_file["id"], local_path - ) - instance.data["shotgridPublishedFile"] = published_file - - def _find_existing_publish(self, code, context, shotgrid_version): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["version", "is", shotgrid_version], - ["code", "is", code], - ] - return self.sg.find_one("PublishedFile", filters, []) - - def _create_published(self, published_file_data): - - return self.sg.create("PublishedFile", published_file_data) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py deleted file mode 100644 index a1b7140e22..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import pyblish.api - - -class IntegrateShotgridVersion(pyblish.api.InstancePlugin): - """Integrate Shotgrid Version""" - - order = pyblish.api.IntegratorOrder + 0.497 - label = "Shotgrid Version" - - sg = None - - def process(self, instance): - - context = instance.context - self.sg = context.data.get("shotgridSession") - - # TODO: Use path template solver to build version code from settings - anatomy = instance.data.get("anatomyData", {}) - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - - version = self._find_existing_version(code, context) - - if not version: - version = self._create_version(code, context) - self.log.info("Create Shotgrid version: {}".format(version)) - else: - self.log.info("Use existing Shotgrid version: {}".format(version)) - - data_to_update = {} - status = context.data.get("intent", {}).get("value") - if status: - data_to_update["sg_status_list"] = status - - for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") - code = os.path.basename(local_path) - - if "shotgridreview" in representation.get("tags", []): - - if representation["ext"] in ["mov", "avi"]: - self.log.info( - "Upload review: {} for version shotgrid {}".format( - local_path, version.get("id") - ) - ) - self.sg.upload( - "Version", - version.get("id"), - local_path, - field_name="sg_uploaded_movie", - ) - - data_to_update["sg_path_to_movie"] = local_path - - elif representation["ext"] in ["jpg", "png", "exr", "tga"]: - path_to_frame = local_path.replace("0000", "#") - data_to_update["sg_path_to_frames"] = path_to_frame - - self.log.info("Update Shotgrid version with {}".format(data_to_update)) - self.sg.update("Version", version["id"], data_to_update) - - instance.data["shotgridVersion"] = version - - def _find_existing_version(self, code, context): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["sg_task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["code", "is", code], - ] - return self.sg.find_one("Version", filters, []) - - def _create_version(self, code, context): - - version_data = { - "project": context.data.get("shotgridProject"), - "sg_task": context.data.get("shotgridTask"), - "entity": context.data.get("shotgridEntity"), - "code": code, - } - - return self.sg.create("Version", version_data) diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py deleted file mode 100644 index c14c980e2a..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py +++ /dev/null @@ -1,38 +0,0 @@ -import pyblish.api -import openpype.api - - -class ValidateShotgridUser(pyblish.api.ContextPlugin): - """ - Check if user is valid and have access to the project. - """ - - label = "Validate Shotgrid User" - order = openpype.api.ValidateContentsOrder - - def process(self, context): - sg = context.data.get("shotgridSession") - - login = context.data.get("shotgridUser") - self.log.info("Login shotgrid set in OpenPype is {}".format(login)) - project = context.data.get("shotgridProject") - self.log.info("Current shotgun project is {}".format(project)) - - if not (login and sg and project): - raise KeyError() - - user = sg.find_one("HumanUser", [["login", "is", login]], ["projects"]) - - self.log.info(user) - self.log.info(login) - user_projects_id = [p["id"] for p in user.get("projects", [])] - if not project.get("id") in user_projects_id: - raise PermissionError( - "Login {} don't have access to the project {}".format( - login, project - ) - ) - - self.log.info( - "Login {} have access to the project {}".format(login, project) - ) diff --git a/openpype/modules/shotgrid/server/README.md b/openpype/modules/shotgrid/server/README.md deleted file mode 100644 index 15e056ff3e..0000000000 --- a/openpype/modules/shotgrid/server/README.md +++ /dev/null @@ -1,5 +0,0 @@ - -### Shotgrid server - -Please refer to the external project that covers Openpype/Shotgrid communication: - - https://github.com/Ellipsanime/shotgrid-leecher diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py deleted file mode 100644 index 5644f0c35f..0000000000 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ /dev/null @@ -1,58 +0,0 @@ -import os - -from openpype_interfaces import ( - ITrayModule, - IPluginPaths, - ILaunchHookPaths, -) - -from openpype.modules import OpenPypeModule - -SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class ShotgridModule( - OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths -): - leecher_manager_url = None - name = "shotgrid" - enabled = False - project_id = None - tray_wrapper = None - - def initialize(self, modules_settings): - shotgrid_settings = modules_settings.get(self.name, dict()) - self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get( - "leecher_manager_url", "" - ) - - def connect_with_modules(self, enabled_modules): - pass - - def get_global_environments(self): - return {"PROJECT_ID": self.project_id} - - def get_plugin_paths(self): - return { - "publish": [ - os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") - ] - } - - def get_launch_hook_paths(self): - return os.path.join(SHOTGRID_MODULE_DIR, "hooks") - - def tray_init(self): - from .tray.shotgrid_tray import ShotgridTrayWrapper - - self.tray_wrapper = ShotgridTrayWrapper(self) - - def tray_start(self): - return self.tray_wrapper.validate() - - def tray_exit(self, *args, **kwargs): - return self.tray_wrapper - - def tray_menu(self, tray_menu): - return self.tray_wrapper.tray_menu(tray_menu) diff --git a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py deleted file mode 100644 index 1f78cf77c9..0000000000 --- a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -from assertpy import assert_that - -import openpype.modules.shotgrid.lib.credentials as sut - - -def test_missing_shotgrid_url(): - with pytest.raises(Exception) as ex: - # arrange - url = "" - # act - sut.get_shotgrid_hostname(url) - # assert - assert_that(ex).is_equal_to("Shotgrid url cannot be a null") - - -def test_full_shotgrid_url(): - # arrange - url = "https://shotgrid.com/myinstance" - # act - actual = sut.get_shotgrid_hostname(url) - # assert - assert_that(actual).is_not_empty() - assert_that(actual).is_equal_to("shotgrid.com") - - -def test_incomplete_shotgrid_url(): - # arrange - url = "shotgrid.com/myinstance" - # act - actual = sut.get_shotgrid_hostname(url) - # assert - assert_that(actual).is_not_empty() - assert_that(actual).is_equal_to("shotgrid.com") diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py deleted file mode 100644 index 9d841d98be..0000000000 --- a/openpype/modules/shotgrid/tray/credential_dialog.py +++ /dev/null @@ -1,201 +0,0 @@ -import os -from Qt import QtCore, QtWidgets, QtGui - -from openpype import style -from openpype import resources -from openpype.modules.shotgrid.lib import settings, credentials - - -class CredentialsDialog(QtWidgets.QDialog): - SIZE_W = 450 - SIZE_H = 200 - - _module = None - _is_logged = False - url_label = None - login_label = None - password_label = None - url_input = None - login_input = None - password_input = None - input_layout = None - login_button = None - buttons_layout = None - main_widget = None - - login_changed = QtCore.Signal() - - def __init__(self, module, parent=None): - super(CredentialsDialog, self).__init__(parent) - - self._module = module - self._is_logged = False - - self.setWindowTitle("OpenPype - Shotgrid Login") - - icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) - self.setWindowIcon(icon) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint - | QtCore.Qt.WindowMinimizeButtonHint - ) - self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) - self.setStyleSheet(style.load_stylesheet()) - - self.ui_init() - - def ui_init(self): - self.url_label = QtWidgets.QLabel("Shotgrid server:") - self.login_label = QtWidgets.QLabel("Login:") - self.password_label = QtWidgets.QLabel("Password:") - - self.url_input = QtWidgets.QComboBox() - # self.url_input.setReadOnly(True) - - self.login_input = QtWidgets.QLineEdit() - self.login_input.setPlaceholderText("login") - - self.password_input = QtWidgets.QLineEdit() - self.password_input.setPlaceholderText("password") - self.password_input.setEchoMode(QtWidgets.QLineEdit.Password) - - self.error_label = QtWidgets.QLabel("") - self.error_label.setStyleSheet("color: red;") - self.error_label.setWordWrap(True) - self.error_label.hide() - - self.input_layout = QtWidgets.QFormLayout() - self.input_layout.setContentsMargins(10, 15, 10, 5) - - self.input_layout.addRow(self.url_label, self.url_input) - self.input_layout.addRow(self.login_label, self.login_input) - self.input_layout.addRow(self.password_label, self.password_input) - self.input_layout.addRow(self.error_label) - - self.login_button = QtWidgets.QPushButton("Login") - self.login_button.setToolTip("Log in shotgrid instance") - self.login_button.clicked.connect(self._on_shotgrid_login_clicked) - - self.logout_button = QtWidgets.QPushButton("Logout") - self.logout_button.setToolTip("Log out shotgrid instance") - self.logout_button.clicked.connect(self._on_shotgrid_logout_clicked) - - self.buttons_layout = QtWidgets.QHBoxLayout() - self.buttons_layout.addWidget(self.logout_button) - self.buttons_layout.addWidget(self.login_button) - - self.main_widget = QtWidgets.QVBoxLayout(self) - self.main_widget.addLayout(self.input_layout) - self.main_widget.addLayout(self.buttons_layout) - self.setLayout(self.main_widget) - - def show(self, *args, **kwargs): - super(CredentialsDialog, self).show(*args, **kwargs) - self._fill_shotgrid_url() - self._fill_shotgrid_login() - - def _fill_shotgrid_url(self): - servers = settings.get_shotgrid_servers() - - if servers: - for _, v in servers.items(): - self.url_input.addItem("{}".format(v.get('shotgrid_url'))) - self._valid_input(self.url_input) - self.login_button.show() - self.logout_button.show() - enabled = True - else: - self.set_error("Ask your admin to add shotgrid server in settings") - self._invalid_input(self.url_input) - self.login_button.hide() - self.logout_button.hide() - enabled = False - - self.login_input.setEnabled(enabled) - self.password_input.setEnabled(enabled) - - def _fill_shotgrid_login(self): - login = credentials.get_local_login() - - if login: - self.login_input.setText(login) - - def _clear_shotgrid_login(self): - self.login_input.setText("") - self.password_input.setText("") - - def _on_shotgrid_login_clicked(self): - login = self.login_input.text().strip() - password = self.password_input.text().strip() - missing = [] - - if login == "": - missing.append("login") - self._invalid_input(self.login_input) - - if password == "": - missing.append("password") - self._invalid_input(self.password_input) - - url = self.url_input.currentText() - if url == "": - missing.append("url") - self._invalid_input(self.url_input) - - if len(missing) > 0: - self.set_error("You didn't enter {}".format(" and ".join(missing))) - return - - # if credentials.check_credentials( - # login=login, - # password=password, - # shotgrid_url=url, - # ): - credentials.save_local_login( - login=login - ) - os.environ['OPENPYPE_SG_USER'] = login - self._on_login() - - self.set_error("CANT LOGIN") - - def _on_shotgrid_logout_clicked(self): - credentials.clear_local_login() - del os.environ['OPENPYPE_SG_USER'] - self._clear_shotgrid_login() - self._on_logout() - - def set_error(self, msg): - self.error_label.setText(msg) - self.error_label.show() - - def _on_login(self): - self._is_logged = True - self.login_changed.emit() - self._close_widget() - - def _on_logout(self): - self._is_logged = False - self.login_changed.emit() - - def _close_widget(self): - self.hide() - - def _valid_input(self, input_widget): - input_widget.setStyleSheet("") - - def _invalid_input(self, input_widget): - input_widget.setStyleSheet("border: 1px solid red;") - - def login_with_credentials( - self, url, login, password - ): - verification = credentials.check_credentials(url, login, password) - if verification: - credentials.save_credentials(login, password, False) - self._module.set_credentials_to_env(login, password) - self.set_credentials(login, password) - self.login_changed.emit() - return verification diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py deleted file mode 100644 index 4038d77b03..0000000000 --- a/openpype/modules/shotgrid/tray/shotgrid_tray.py +++ /dev/null @@ -1,75 +0,0 @@ -import os -import webbrowser - -from Qt import QtWidgets - -from openpype.modules.shotgrid.lib import credentials -from openpype.modules.shotgrid.tray.credential_dialog import ( - CredentialsDialog, -) - - -class ShotgridTrayWrapper: - module = None - credentials_dialog = None - logged_user_label = None - - def __init__(self, module): - self.module = module - self.credentials_dialog = CredentialsDialog(module) - self.credentials_dialog.login_changed.connect(self.set_login_label) - self.logged_user_label = QtWidgets.QAction("") - self.logged_user_label.setDisabled(True) - self.set_login_label() - - def show_batch_dialog(self): - if self.module.leecher_manager_url: - webbrowser.open(self.module.leecher_manager_url) - - def show_connect_dialog(self): - self.show_credential_dialog() - - def show_credential_dialog(self): - self.credentials_dialog.show() - self.credentials_dialog.activateWindow() - self.credentials_dialog.raise_() - - def set_login_label(self): - login = credentials.get_local_login() - if login: - self.logged_user_label.setText("{}".format(login)) - else: - self.logged_user_label.setText( - "No User logged in {0}".format(login) - ) - - def tray_menu(self, tray_menu): - # Add login to user menu - menu = QtWidgets.QMenu("Shotgrid", tray_menu) - show_connect_action = QtWidgets.QAction("Connect to Shotgrid", menu) - show_connect_action.triggered.connect(self.show_connect_dialog) - menu.addAction(self.logged_user_label) - menu.addSeparator() - menu.addAction(show_connect_action) - tray_menu.addMenu(menu) - - # Add manager to Admin menu - for m in tray_menu.findChildren(QtWidgets.QMenu): - if m.title() == "Admin": - shotgrid_manager_action = QtWidgets.QAction( - "Shotgrid manager", menu - ) - shotgrid_manager_action.triggered.connect( - self.show_batch_dialog - ) - m.addAction(shotgrid_manager_action) - - def validate(self): - login = credentials.get_local_login() - - if not login: - self.show_credential_dialog() - else: - os.environ["OPENPYPE_SG_USER"] = login - - return True diff --git a/openpype/resources/app_icons/shotgrid.png b/openpype/resources/app_icons/shotgrid.png deleted file mode 100644 index 6d0cc047f9ed86e0db45ea557404ab7edb2f5bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45744 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 N8$~sR3OTc&{{b(fTU!7C diff --git a/openpype/settings/defaults/project_settings/shotgrid.json b/openpype/settings/defaults/project_settings/shotgrid.json deleted file mode 100644 index 83b6f69074..0000000000 --- a/openpype/settings/defaults/project_settings/shotgrid.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "shotgrid_project_id": 0, - "shotgrid_server": "", - "event": { - "enabled": false - }, - "fields": { - "asset": { - "type": "sg_asset_type" - }, - "sequence": { - "episode_link": "episode" - }, - "shot": { - "episode_link": "sg_episode", - "sequence_link": "sg_sequence" - }, - "task": { - "step": "step" - } - } -} diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 9d8910689a..8cd4114cb0 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -131,12 +131,6 @@ } } }, - "shotgrid": { - "enabled": false, - "leecher_manager_url": "http://127.0.0.1:3000", - "leecher_backend_url": "http://127.0.0.1:8090", - "shotgrid_settings": {} - }, "kitsu": { "enabled": false, "server": "" @@ -209,4 +203,4 @@ "linux": "" } } -} +} \ No newline at end of file diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index b2cb2204f4..a173e2454f 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -107,7 +107,6 @@ from .enum_entity import ( TaskTypeEnumEntity, DeadlineUrlEnumEntity, AnatomyTemplatesEnumEntity, - ShotgridUrlEnumEntity ) from .list_entity import ListEntity @@ -172,7 +171,6 @@ __all__ = ( "ToolsEnumEntity", "TaskTypeEnumEntity", "DeadlineUrlEnumEntity", - "ShotgridUrlEnumEntity", "AnatomyTemplatesEnumEntity", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 3b3dd47e61..92a397afba 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -1,7 +1,10 @@ import copy from .input_entities import InputEntity from .exceptions import EntitySchemaError -from .lib import NOT_SET, STRING_TYPE +from .lib import ( + NOT_SET, + STRING_TYPE +) class BaseEnumEntity(InputEntity): @@ -23,7 +26,7 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = 'Key "{}" is more than once in enum items.'.format( + reason = "Key \"{}\" is more than once in enum items.".format( key ) raise EntitySchemaError(self, reason) @@ -31,7 +34,7 @@ class BaseEnumEntity(InputEntity): enum_keys.add(key) if not isinstance(key, STRING_TYPE): - reason = 'Key "{}" has invalid type {}, expected {}.'.format( + reason = "Key \"{}\" has invalid type {}, expected {}.".format( key, type(key), STRING_TYPE ) raise EntitySchemaError(self, reason) @@ -56,7 +59,7 @@ class BaseEnumEntity(InputEntity): for item in check_values: if item not in self.valid_keys: raise ValueError( - '{} Invalid value "{}". Expected one of: {}'.format( + "{} Invalid value \"{}\". Expected one of: {}".format( self.path, item, self.valid_keys ) ) @@ -81,7 +84,7 @@ class EnumEntity(BaseEnumEntity): self.valid_keys = set(all_keys) if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) value_on_not_set = [] if enum_default: if not isinstance(enum_default, list): @@ -106,7 +109,7 @@ class EnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -149,7 +152,6 @@ class HostsEnumEntity(BaseEnumEntity): Host name is not the same as application name. Host name defines implementation instead of application name. """ - schema_types = ["hosts-enum"] all_host_names = [ "aftereffects", @@ -167,7 +169,7 @@ class HostsEnumEntity(BaseEnumEntity): "tvpaint", "unreal", "standalonepublisher", - "webpublisher", + "webpublisher" ] def _item_initialization(self): @@ -208,7 +210,7 @@ class HostsEnumEntity(BaseEnumEntity): self.valid_keys = valid_keys if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.value_on_not_set = [] else: for key in valid_keys: @@ -216,7 +218,7 @@ class HostsEnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -224,10 +226,14 @@ class HostsEnumEntity(BaseEnumEntity): def schema_validations(self): if self.hosts_filter: enum_len = len(self.enum_items) - if enum_len == 0 or (enum_len == 1 and self.use_empty_value): - joined_filters = ", ".join( - ['"{}"'.format(item) for item in self.hosts_filter] - ) + if ( + enum_len == 0 + or (enum_len == 1 and self.use_empty_value) + ): + joined_filters = ", ".join([ + '"{}"'.format(item) + for item in self.hosts_filter + ]) reason = ( "All host names were removed after applying" " host filters. {}" @@ -240,25 +246,24 @@ class HostsEnumEntity(BaseEnumEntity): invalid_filters.add(item) if invalid_filters: - joined_filters = ", ".join( - ['"{}"'.format(item) for item in self.hosts_filter] - ) - expected_hosts = ", ".join( - ['"{}"'.format(item) for item in self.all_host_names] - ) - self.log.warning( - ( - "Host filters containt invalid host names:" - ' "{}" Expected values are {}' - ).format(joined_filters, expected_hosts) - ) + joined_filters = ", ".join([ + '"{}"'.format(item) + for item in self.hosts_filter + ]) + expected_hosts = ", ".join([ + '"{}"'.format(item) + for item in self.all_host_names + ]) + self.log.warning(( + "Host filters containt invalid host names:" + " \"{}\" Expected values are {}" + ).format(joined_filters, expected_hosts)) super(HostsEnumEntity, self).schema_validations() class AppsEnumEntity(BaseEnumEntity): """Enum of applications for project anatomy attributes.""" - schema_types = ["apps-enum"] def _item_initialization(self): @@ -266,7 +271,7 @@ class AppsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.placeholder = None def _get_enum_values(self): @@ -347,7 +352,7 @@ class ToolsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.placeholder = None def _get_enum_values(self): @@ -404,10 +409,10 @@ class TaskTypeEnumEntity(BaseEnumEntity): def _item_initialization(self): self.multiselection = self.schema_data.get("multiselection", True) if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.value_on_not_set = [] else: - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) self.value_on_not_set = "" self.enum_items = [] @@ -502,8 +507,7 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): enum_items_list = [] for server_name, url_entity in deadline_urls_entity.items(): enum_items_list.append( - {server_name: "{}: {}".format(server_name, url_entity.value)} - ) + {server_name: "{}: {}".format(server_name, url_entity.value)}) valid_keys.add(server_name) return enum_items_list, valid_keys @@ -526,50 +530,6 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): self._current_value = tuple(self.valid_keys)[0] -class ShotgridUrlEnumEntity(BaseEnumEntity): - schema_types = ["shotgrid_url-enum"] - - def _item_initialization(self): - self.multiselection = False - - self.enum_items = [] - self.valid_keys = set() - - self.valid_value_types = (STRING_TYPE,) - self.value_on_not_set = "" - - # GUI attribute - self.placeholder = self.schema_data.get("placeholder") - - def _get_enum_values(self): - shotgrid_settings = self.get_entity_from_path( - "system_settings/modules/shotgrid/shotgrid_settings" - ) - - valid_keys = set() - enum_items_list = [] - for server_name, settings in shotgrid_settings.items(): - enum_items_list.append( - { - server_name: "{}: {}".format( - server_name, settings["shotgrid_url"].value - ) - } - ) - valid_keys.add(server_name) - return enum_items_list, valid_keys - - def set_override_state(self, *args, **kwargs): - super(ShotgridUrlEnumEntity, self).set_override_state(*args, **kwargs) - - self.enum_items, self.valid_keys = self._get_enum_values() - if not self.valid_keys: - self._current_value = "" - - elif self._current_value not in self.valid_keys: - self._current_value = tuple(self.valid_keys)[0] - - class AnatomyTemplatesEnumEntity(BaseEnumEntity): schema_types = ["anatomy-templates-enum"] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 80b1baad1b..6c07209de3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -62,10 +62,6 @@ "type": "schema", "name": "schema_project_ftrack" }, - { - "type": "schema", - "name": "schema_project_shotgrid" - }, { "type": "schema", "name": "schema_project_kitsu" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json deleted file mode 100644 index 4faeca89f3..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "type": "dict", - "key": "shotgrid", - "label": "Shotgrid", - "collapsible": true, - "is_file": true, - "children": [ - { - "type": "number", - "key": "shotgrid_project_id", - "label": "Shotgrid project id" - }, - { - "type": "shotgrid_url-enum", - "key": "shotgrid_server", - "label": "Shotgrid Server" - }, - { - "type": "dict", - "key": "event", - "label": "Event Handler", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, - { - "type": "dict", - "key": "fields", - "label": "Fields Template", - "collapsible": true, - "children": [ - { - "type": "dict", - "key": "asset", - "label": "Asset", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "type", - "label": "Asset Type" - } - ] - }, - { - "type": "dict", - "key": "sequence", - "label": "Sequence", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "episode_link", - "label": "Episode link" - } - ] - }, - { - "type": "dict", - "key": "shot", - "label": "Shot", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "episode_link", - "label": "Episode link" - }, - { - "type": "text", - "key": "sequence_link", - "label": "Sequence link" - } - ] - }, - { - "type": "dict", - "key": "task", - "label": "Task", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "step", - "label": "Step link" - } - ] - } - ] - } - ] -} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index a4b28f47bc..484fbf9d07 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -13,9 +13,6 @@ { "ftrackreview": "Add review to Ftrack" }, - { - "shotgridreview": "Add review to Shotgrid" - }, { "delete": "Delete output" }, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 952b38040c..d22b9016a7 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -48,60 +48,6 @@ "type": "schema", "name": "schema_kitsu" }, - { - "type": "dict", - "key": "shotgrid", - "label": "Shotgrid", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "leecher_manager_url", - "label": "Shotgrid Leecher Manager URL" - }, - { - "type": "text", - "key": "leecher_backend_url", - "label": "Shotgrid Leecher Backend URL" - }, - { - "type": "boolean", - "key": "filter_projects_by_login", - "label": "Filter projects by SG login" - }, - { - "type": "dict-modifiable", - "key": "shotgrid_settings", - "label": "Shotgrid Servers", - "object_type": { - "type": "dict", - "children": [ - { - "key": "shotgrid_url", - "label": "Server URL", - "type": "text" - }, - { - "key": "shotgrid_script_name", - "label": "Script Name", - "type": "text" - }, - { - "key": "shotgrid_script_key", - "label": "Script api key", - "type": "text" - } - ] - } - } - ] - }, { "type": "dict", "key": "timers_manager", diff --git a/poetry.lock b/poetry.lock index f6ccf1ffc9..47509f334e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1362,21 +1362,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "shotgun-api3" -version = "3.3.3" -description = "Shotgun Python API" -category = "main" -optional = false -python-versions = "*" -develop = false - -[package.source] -type = "git" -url = "https://github.com/shotgunsoftware/python-api.git" -reference = "v3.3.3" -resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" - [[package]] name = "six" version = "1.16.0" @@ -2818,7 +2803,6 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index c68de91623..4b297fe042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,6 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" -shotgun_api3 = {git = "https://github.com/shotgunsoftware/python-api.git", rev = "v3.3.3"} gazu = "^0.8.28" google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" From da89c2d06bf04376e85ddd9594740067e99009e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:53:50 +0200 Subject: [PATCH 256/258] :pencil2: fixing typo --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index e8ada57f8f..ec583bcce7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -40,7 +40,7 @@ FILE_NODES = { "aiImage": "filename", - "RedshiftNormalMap": "text0", + "RedshiftNormalMap": "tex0", "PxrBump": "filename", "PxrNormalMap": "filename", From 1ed58ef89cd17b3485b38292e3774dfacba6c36c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 16:16:37 +0200 Subject: [PATCH 257/258] use query functions in hiero --- openpype/hosts/hiero/api/lib.py | 80 +++++++++++++------ openpype/hosts/hiero/api/tags.py | 9 ++- .../hosts/hiero/plugins/load/load_clip.py | 37 ++++----- .../collect_assetbuilds.py | 4 +- 4 files changed, 84 insertions(+), 46 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 06dfd2f2ee..482ddafa4d 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -14,6 +14,12 @@ import hiero from Qt import QtWidgets from bson.objectid import ObjectId +from openpype.client import ( + get_project, + get_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.api import (Logger, Anatomy, get_anatomy_settings) from . import tags @@ -477,7 +483,7 @@ def sync_avalon_data_to_workfile(): project.setProjectRoot(active_project_root) # get project data from avalon db - project_doc = legacy_io.find_one({"type": "project"}) + project_doc = get_project(project_name) project_data = project_doc["data"] log.debug("project_data: {}".format(project_data)) @@ -1065,35 +1071,63 @@ def check_inventory_versions(track_items=None): clip_color_last = "green" clip_color = "red" - # get all track items from current timeline + item_with_repre_id = [] + repre_ids = set() + # Find all containers and collect it's node and representation ids for track_item in track_item: container = parse_container(track_item) if container: - # get representation from io - representation = legacy_io.find_one({ - "type": "representation", - "_id": ObjectId(container["representation"]) - }) + repre_id = container["representation"] + repre_ids.add(repre_id) + item_with_repre_id.append((track_item, repre_id)) - # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + # Skip if nothing was found + if not repre_ids: + return - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') + project_name = legacy_io.active_project() + # Find representations based on found containers + repre_docs = get_representations( + project_name, + repre_ids=repre_ids, + fields=["_id", "parent"] + ) + # Store representations by id and collect version ids + repre_docs_by_id = {} + version_ids = set() + for repre_doc in repre_docs: + # Use stringed representation id to match value in containers + repre_id = str(repre_doc["_id"]) + repre_docs_by_id[repre_id] = repre_doc + version_ids.add(repre_doc["parent"]) - max_version = max(versions) + version_docs = get_versions( + project_name, version_ids, fields=["_id", "name", "parent"] + ) + # Store versions by id and collect subset ids + version_docs_by_id = {} + subset_ids = set() + for version_doc in version_docs: + version_docs_by_id[version_doc["_id"]] = version_doc + subset_ids.add(version_doc["parent"]) - # set clip colour - if version.get("name") == max_version: - track_item.source().binItem().setColor(clip_color_last) - else: - track_item.source().binItem().setColor(clip_color) + # Query last versions based on subset ids + last_versions_by_subset_id = get_last_versions( + project_name, subset_ids=subset_ids, fields=["_id", "parent"] + ) + + for item in item_with_repre_id: + # Some python versions of nuke can't unfold tuple in for loop + track_item, repre_id = item + + repre_doc = repre_docs_by_id[repre_id] + version_doc = version_docs_by_id[repre_doc["parent"]] + last_version_doc = last_versions_by_subset_id[version_doc["parent"]] + # Check if last version is same as current version + if version_doc["_id"] == last_version_doc["_id"]: + track_item.source().binItem().setColor(clip_color_last) + else: + track_item.source().binItem().setColor(clip_color) def selection_changed_timeline(event): diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index 8c6ff2a77b..10df96fa53 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -2,6 +2,7 @@ import re import os import hiero +from openpype.client import get_project, get_assets from openpype.api import Logger from openpype.pipeline import legacy_io @@ -141,7 +142,9 @@ def add_tags_to_workfile(): nks_pres_tags = tag_data() # Get project task types. - tasks = legacy_io.find_one({"type": "project"})["config"]["tasks"] + project_name = legacy_io.active_project() + project_doc = get_project(project_name) + tasks = project_doc["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} log.debug("__ tasks: {}".format(tasks)) for task_type in tasks.keys(): @@ -159,7 +162,9 @@ def add_tags_to_workfile(): # asset builds and shots. if int(os.getenv("TAG_ASSETBUILD_STARTUP", 0)) == 1: nks_pres_tags["[AssetBuilds]"] = {} - for asset in legacy_io.find({"type": "asset"}): + for asset in get_assets( + project_name, fields=["name", "data.entityType"] + ): if asset["data"]["entityType"] == "AssetBuild": nks_pres_tags["[AssetBuilds]"][asset["name"]] = { "editable": "1", diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index a3365253b3..2a7d1af41e 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -1,3 +1,7 @@ +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id +) from openpype.pipeline import ( legacy_io, get_representation_path, @@ -103,12 +107,12 @@ class LoadClip(phiero.SequenceLoader): namespace = container['namespace'] track_item = phiero.get_track_items( track_item_name=namespace).pop() - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - version_data = version.get("data", {}) - version_name = version.get("name", None) + + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + + version_data = version_doc.get("data", {}) + version_name = version_doc.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) file = get_representation_path(representation).replace("\\", "/") @@ -143,7 +147,7 @@ class LoadClip(phiero.SequenceLoader): }) # update color of clip regarding the version order - self.set_item_color(track_item, version) + self.set_item_color(track_item, version_doc) return phiero.update_container(track_item, data_imprint) @@ -166,21 +170,14 @@ class LoadClip(phiero.SequenceLoader): cls.sequence = cls.track.parent() @classmethod - def set_item_color(cls, track_item, version): - + def set_item_color(cls, track_item, version_doc): + project_name = legacy_io.active_project() + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) clip = track_item.source() - # define version name - version_name = version.get("name", None) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - # set clip colour - if version_name == max_version: + if version_doc["_id"] == last_version_doc["_id"]: clip.binItem().setColor(cls.clip_color_last) else: clip.binItem().setColor(cls.clip_color) diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py index 10baf25803..5f96533052 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py @@ -1,4 +1,5 @@ from pyblish import api +from openpype.client import get_assets from openpype.pipeline import legacy_io @@ -17,8 +18,9 @@ class CollectAssetBuilds(api.ContextPlugin): hosts = ["hiero"] def process(self, context): + project_name = legacy_io.active_project() asset_builds = {} - for asset in legacy_io.find({"type": "asset"}): + for asset in get_assets(project_name): if asset["data"]["entityType"] == "AssetBuild": self.log.debug("Found \"{}\" in database.".format(asset)) asset_builds[asset["name"]] = asset From 52179d4015e981e4c0cdd56c979e230e61ecc7d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 16:17:07 +0200 Subject: [PATCH 258/258] remove unused import --- openpype/hosts/hiero/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 482ddafa4d..8c8c31bc4c 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -12,7 +12,6 @@ import shutil import hiero from Qt import QtWidgets -from bson.objectid import ObjectId from openpype.client import ( get_project,