From f6495ca956c709cf33654d12c80cadedb5a272d3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Nov 2022 13:30:41 +0100 Subject: [PATCH 01/58] OP-4394 - extension is lowercased in Setting and in uploaded files --- .../webpublisher/plugins/publish/collect_published_files.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 2bf097de418..ac4ade4e48e 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -86,6 +86,7 @@ def process(self, context): first_file = task_data["files"][0] _, extension = os.path.splitext(first_file) + extension = extension.lower() family, families, tags = self._get_family( self.task_type_to_family, task_type, @@ -244,7 +245,10 @@ def _get_family(self, settings, task_type, is_sequence, extension): for config in families_config: if is_sequence != config["is_sequence"]: continue - if (extension in config["extensions"] or + + lower_extensions = [ext.lower() + for ext in config.get("extensions", [])] + if (extension.lower() in lower_extensions or '' in config["extensions"]): # all extensions setting found_family = config["result_family"] break From 93b9dd7224e669c4f453dc0578ebc57ce0812c6f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Nov 2022 13:40:28 +0100 Subject: [PATCH 02/58] OP-4394 - extension is lowercased in Setting and in uploaded files --- .../webpublisher/plugins/publish/collect_published_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index ac4ade4e48e..40f4da9403a 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -247,9 +247,9 @@ def _get_family(self, settings, task_type, is_sequence, extension): continue lower_extensions = [ext.lower() - for ext in config.get("extensions", [])] + for ext in config.get("extensions", [''])] if (extension.lower() in lower_extensions or - '' in config["extensions"]): # all extensions setting + lower_extensions[0] == ''): # all extensions setting found_family = config["result_family"] break From f18efd29b2aebe89f3cc8dbbbf03dc9bdfdff5b2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Nov 2022 17:36:30 +0100 Subject: [PATCH 03/58] OP-4394 - fix - lowercase extension everywhere Without it it would be stored in DB uppercased and final name would also be uppercased. --- .../webpublisher/plugins/publish/collect_published_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 40f4da9403a..265e78a6c70 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -181,6 +181,7 @@ def _get_subset_name(self, family, subset_template, task_name, variant): def _get_single_repre(self, task_dir, files, tags): _, ext = os.path.splitext(files[0]) + ext = ext.lower() repre_data = { "name": ext[1:], "ext": ext[1:], @@ -200,6 +201,7 @@ def _process_sequence(self, files, task_dir, tags): frame_start = list(collections[0].indexes)[0] frame_end = list(collections[0].indexes)[-1] ext = collections[0].tail + ext = ext.lower() repre_data = { "frameStart": frame_start, "frameEnd": frame_end, From e11815b663d3910032fc6f2ec492df857ce91590 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 14:41:35 +0100 Subject: [PATCH 04/58] OP-4394 - safer handling of Settings extensions Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/collect_published_files.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 265e78a6c70..181f8b4ab72 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -247,11 +247,17 @@ def _get_family(self, settings, task_type, is_sequence, extension): for config in families_config: if is_sequence != config["is_sequence"]: continue - - lower_extensions = [ext.lower() - for ext in config.get("extensions", [''])] - if (extension.lower() in lower_extensions or - lower_extensions[0] == ''): # all extensions setting + extensions = config.get("extensions") or [] + lower_extensions = set() + for ext in extensions: + if ext: + ext = ext.lower() + if ext.startswith("."): + ext = ext[1:] + lower_extensions.add(ext) + + # all extensions setting + if not lower_extensions or extension in lower_extensions: found_family = config["result_family"] break From ed7795061f946ca71e7c3b09977c68525e3cd24c Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 12 Nov 2022 03:44:20 +0000 Subject: [PATCH 05/58] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 81b2925fb53..1953d0d6a5c 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.3" +__version__ = "3.14.7-nightly.4" From f9732a8385a75384d91a424ac007285a082c9a2a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 18:52:59 +0100 Subject: [PATCH 06/58] renamed 'CollectAvalonEntities' to 'CollectContextEntities' --- .../hosts/tvpaint/plugins/publish/collect_instance_frames.py | 2 +- openpype/hosts/tvpaint/plugins/publish/validate_marks.py | 2 +- .../plugins/publish/validate_tvpaint_workfile_data.py | 2 +- ...collect_avalon_entities.py => collect_context_entities.py} | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename openpype/plugins/publish/{collect_avalon_entities.py => collect_context_entities.py} (97%) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py index f291c363b8e..d5b79758add 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -6,7 +6,7 @@ class CollectOutputFrameRange(pyblish.api.ContextPlugin): When instances are collected context does not contain `frameStart` and `frameEnd` keys yet. They are collected in global plugin - `CollectAvalonEntities`. + `CollectContextEntities`. """ label = "Collect output frame range" order = pyblish.api.CollectorOrder diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index 12d50e17fff..0030b0fd1ce 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -39,7 +39,7 @@ class ValidateMarks(pyblish.api.ContextPlugin): def get_expected_data(context): scene_mark_in = context.data["sceneMarkIn"] - # Data collected in `CollectAvalonEntities` + # Data collected in `CollectContextEntities` frame_end = context.data["frameEnd"] frame_start = context.data["frameStart"] handle_start = context.data["handleStart"] diff --git a/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py b/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py index a5e4868411b..d8b7bb90781 100644 --- a/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py +++ b/openpype/hosts/webpublisher/plugins/publish/validate_tvpaint_workfile_data.py @@ -13,7 +13,7 @@ class ValidateWorkfileData(pyblish.api.ContextPlugin): targets = ["tvpaint_worker"] def process(self, context): - # Data collected in `CollectAvalonEntities` + # Data collected in `CollectContextEntities` frame_start = context.data["frameStart"] frame_end = context.data["frameEnd"] handle_start = context.data["handleStart"] diff --git a/openpype/plugins/publish/collect_avalon_entities.py b/openpype/plugins/publish/collect_context_entities.py similarity index 97% rename from openpype/plugins/publish/collect_avalon_entities.py rename to openpype/plugins/publish/collect_context_entities.py index 3b05b6ae987..0a6072a8200 100644 --- a/openpype/plugins/publish/collect_avalon_entities.py +++ b/openpype/plugins/publish/collect_context_entities.py @@ -16,11 +16,11 @@ from openpype.pipeline import legacy_io, KnownPublishError -class CollectAvalonEntities(pyblish.api.ContextPlugin): +class CollectContextEntities(pyblish.api.ContextPlugin): """Collect Anatomy into Context.""" order = pyblish.api.CollectorOrder - 0.1 - label = "Collect Avalon Entities" + label = "Collect Context Entities" def process(self, context): legacy_io.install() From 910b7d7120be3982548bf8913410cbb71669f0e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 18:53:34 +0100 Subject: [PATCH 07/58] get "asset" and "task" from context --- openpype/plugins/publish/collect_context_entities.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_context_entities.py b/openpype/plugins/publish/collect_context_entities.py index 0a6072a8200..31fbeb5dbd3 100644 --- a/openpype/plugins/publish/collect_context_entities.py +++ b/openpype/plugins/publish/collect_context_entities.py @@ -3,6 +3,8 @@ Requires: session -> AVALON_ASSET context -> projectName + context -> asset + context -> task Provides: context -> projectEntity - Project document from database. @@ -13,20 +15,19 @@ import pyblish.api from openpype.client import get_project, get_asset_by_name -from openpype.pipeline import legacy_io, KnownPublishError +from openpype.pipeline import KnownPublishError class CollectContextEntities(pyblish.api.ContextPlugin): - """Collect Anatomy into Context.""" + """Collect entities into Context.""" order = pyblish.api.CollectorOrder - 0.1 label = "Collect Context Entities" def process(self, context): - legacy_io.install() project_name = context.data["projectName"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] + asset_name = context.data["asset"] + task_name = context.data["task"] project_entity = get_project(project_name) if not project_entity: From 017720d754d5912d4df5a233168c64f3caccd56f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Nov 2022 19:00:27 +0100 Subject: [PATCH 08/58] get "task" from context in anatomy context data --- openpype/plugins/publish/collect_anatomy_context_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index 8433816908a..55ce8e06f49 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -15,7 +15,6 @@ import json import pyblish.api -from openpype.pipeline import legacy_io from openpype.pipeline.template_data import get_template_data @@ -53,7 +52,7 @@ def process(self, context): asset_entity = context.data.get("assetEntity") task_name = None if asset_entity: - task_name = legacy_io.Session["AVALON_TASK"] + task_name = context.data["task"] anatomy_data = get_template_data( project_entity, asset_entity, task_name, host_name, system_settings From db6bfcb1ee978a3aa77ed00b1dbc2462715a187b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 03:59:40 +0000 Subject: [PATCH 09/58] Bump terser from 5.10.0 to 5.14.2 in /website Bumps [terser](https://github.com/terser/terser) from 5.10.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 64 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 7af15e9145a..177a4a38021 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -1543,15 +1543,37 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.0": version "0.3.4" @@ -1561,6 +1583,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" @@ -2140,10 +2170,10 @@ acorn@^6.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^8.0.4, acorn@^8.4.1: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== address@^1.0.1, address@^1.1.2: version "1.1.2" @@ -6843,11 +6873,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -7053,12 +7078,13 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: terser "^5.7.2" terser@^5.10.0, terser@^5.7.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" text-table@^0.2.0: From ff760342c7719238e9ae06f9bb23c8747cabb615 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Nov 2022 18:27:11 +0100 Subject: [PATCH 10/58] ignore case sensitivity of extension in files widget --- openpype/lib/attribute_definitions.py | 7 +++++++ openpype/tools/attribute_defs/files_widget.py | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 589a4ef9ab7..6baeaec045f 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -541,6 +541,13 @@ def ext(self): return ext return None + @property + def lower_ext(self): + ext = self.ext + if ext is not None: + return ext.lower() + return ext + @property def is_dir(self): if self.is_empty: diff --git a/openpype/tools/attribute_defs/files_widget.py b/openpype/tools/attribute_defs/files_widget.py index 3f1e6a34e15..738e50ba071 100644 --- a/openpype/tools/attribute_defs/files_widget.py +++ b/openpype/tools/attribute_defs/files_widget.py @@ -349,7 +349,7 @@ def _create_item(self, file_item): item.setData(file_item.filenames, FILENAMES_ROLE) item.setData(file_item.directory, DIRPATH_ROLE) item.setData(icon_pixmap, ITEM_ICON_ROLE) - item.setData(file_item.ext, EXT_ROLE) + item.setData(file_item.lower_ext, EXT_ROLE) item.setData(file_item.is_dir, IS_DIR_ROLE) item.setData(file_item.is_sequence, IS_SEQUENCE_ROLE) @@ -463,7 +463,7 @@ def are_valid_files(self, filepaths): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: return True elif self._allow_folders: @@ -475,7 +475,7 @@ def filter_valid_files(self, filepaths): for filepath in filepaths: if os.path.isfile(filepath): _, ext = os.path.splitext(filepath) - if ext in self._allowed_extensions: + if ext.lower() in self._allowed_extensions: filtered_paths.append(filepath) elif self._allow_folders: From 213c78b9a019ac3a8956718e19564b9d5bdfa067 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 00:16:06 +0200 Subject: [PATCH 11/58] Avoid name conflict where `group_name != group_node` due to maya auto renaming new node --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 090047e22db..5ba381050a2 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -73,8 +73,8 @@ def load(self, context, name=None, namespace=None, data=None): c = colors.get(family) if c is not None: - cmds.setAttr(group_name + ".useOutlinerColor", 1) - cmds.setAttr(group_name + ".outlinerColor", + cmds.setAttr(group_node + ".useOutlinerColor", 1) + cmds.setAttr(group_node + ".outlinerColor", (float(c[0])/255), (float(c[1])/255), (float(c[2])/255) From d662b34ca7a70ddb797b4aef4d570028c23a5031 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Nov 2022 13:26:22 +0100 Subject: [PATCH 12/58] added settings for validate frame range in tray publisher --- .../project_settings/traypublisher.json | 7 +++++ .../schema_project_traypublisher.json | 18 +++++++++++++ .../schemas/template_validate_plugin.json | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 5db2a797725..e99b96b8c49 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -303,5 +303,12 @@ "extensions": [ ".mov" ] + }, + "publish": { + "ValidateFrameRange": { + "enabled": true, + "optional": true, + "active": true + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 7c61aeed507..faa5033d2a6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -311,6 +311,24 @@ "object_type": "text" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "schema_template", + "name": "template_validate_plugin", + "template_data": [ + { + "key": "ValidateFrameRange", + "label": "Validate frame range" + } + ] + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json new file mode 100644 index 00000000000..b57cad67195 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_validate_plugin.json @@ -0,0 +1,26 @@ +[ + { + "type": "dict", + "collapsible": true, + "key": "{key}", + "label": "{label}", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + } +] From 125d0bbeed7b07640bc34dd877dac2e4c814895f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 31 Oct 2022 13:28:50 +0100 Subject: [PATCH 13/58] Feature: Auto download last published workfile as first workfile --- .../hooks/pre_copy_last_published_workfile.py | 124 ++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 9 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 openpype/hooks/pre_copy_last_published_workfile.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py new file mode 100644 index 00000000000..004f9d25e73 --- /dev/null +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -0,0 +1,124 @@ +import gc +import os +import shutil +from openpype.client.entities import ( + get_last_version_by_subset_id, + get_representations, + get_subsets, +) +from openpype.lib import PreLaunchHook +from openpype.modules.base import ModulesManager +from openpype.pipeline.load.utils import get_representation_path + + +class CopyLastPublishedWorkfile(PreLaunchHook): + """Copy last published workfile as first workfile. + + Prelaunch hook works only if last workfile leads to not existing file. + - That is possible only if it's first version. + """ + + # Before `AddLastWorkfileToLaunchArgs` + order = -1 + app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"] + + def execute(self): + """Check if local workfile doesn't exist, else copy it. + + 1- Check if setting for this feature is enabled + 2- Check if workfile in work area doesn't exist + 3- Check if published workfile exists and is copied locally in publish + + Returns: + None: This is a void method. + """ + # TODO setting + self.log.info("Trying to fetch last published workfile...") + + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + project_name = self.data["project_name"] + task_name = self.data["task_name"] + + project_doc = self.data.get("project_doc") + asset_doc = self.data.get("asset_doc") + anatomy = self.data.get("anatomy") + if project_doc and asset_doc: + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], + ) + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + return + + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + )["_id"] + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) + + if workfile_representation: # TODO add setting + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) + + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() + + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() + + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 8b11055e655..def9e6cfd8e 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -236,7 +236,11 @@ class SyncServerThread(threading.Thread): """ def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - super(SyncServerThread, self).__init__() + + # Event to trigger files have been processed + self.files_processed = threading.Event() + + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None self.is_running = False @@ -396,6 +400,8 @@ async def sync_loop(self): representation, site, error) + # Trigger files are processed + self.files_processed.set() duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -454,6 +460,7 @@ async def check_shutdown(self): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" + self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): From af15b0d9415d1bfd2bff978ad81d370484d36bdb Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:00:26 +0100 Subject: [PATCH 14/58] Project setting --- .../hooks/pre_copy_last_published_workfile.py | 119 ++++++++++++------ .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 5 + 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 004f9d25e73..312548d2db8 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -7,8 +7,10 @@ get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.profiles_filtering import filter_profiles from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path +from openpype.settings.lib import get_project_settings class CopyLastPublishedWorkfile(PreLaunchHook): @@ -32,9 +34,45 @@ def execute(self): Returns: None: This is a void method. """ - # TODO setting + project_name = self.data["project_name"] + task_name = self.data["task_name"] + task_type = self.data["task_type"] + host_name = self.application.host_name + + # Check settings has enabled it + project_settings = get_project_settings(project_name) + profiles = project_settings["global"]["tools"]["Workfiles"][ + "last_workfile_on_startup" + ] + filter_data = { + "tasks": task_name, + "task_types": task_type, + "hosts": host_name, + } + last_workfile_settings = filter_profiles(profiles, filter_data) + use_last_published_workfile = last_workfile_settings.get( + "use_last_published_workfile" + ) + if use_last_published_workfile is None: + self.log.info( + ( + "Seems like old version of settings is used." + ' Can\'t access custom templates in host "{}".' + ).format(host_name) + ) + return + elif use_last_published_workfile is False: + self.log.info( + ( + 'Project "{}" has turned off to use last published workfile' + ' as first workfile for host "{}"' + ).format(project_name, host_name) + ) + return + self.log.info("Trying to fetch last published workfile...") + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): self.log.debug( @@ -44,9 +82,6 @@ def execute(self): ) return - project_name = self.data["project_name"] - task_name = self.data["task_name"] - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") @@ -65,6 +100,9 @@ def execute(self): None, ) if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) return # Get workfile representation @@ -84,41 +122,46 @@ def execute(self): None, ) - if workfile_representation: # TODO add setting - # Get sync server from Tray, which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Get sync server from Tray, which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) + + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9c3f2f1e1b1..7daa4afa79d 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -458,7 +458,8 @@ "hosts": [], "task_types": [], "tasks": [], - "enabled": true + "enabled": true, + "use_last_published_workfile": false } ], "open_workfile_tool_on_startup": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index ba446135e2f..962008d4764 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -149,6 +149,11 @@ "type": "boolean", "key": "enabled", "label": "Enabled" + }, + { + "type": "boolean", + "key": "use_last_published_workfile", + "label": "Use last published workfile" } ] } From 7a7c91c418f1084dacd25e6aa453e0c70caf9fcd Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:10:12 +0100 Subject: [PATCH 15/58] docstring --- openpype/hooks/pre_copy_last_published_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 312548d2db8..b1b2fe2366d 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -30,6 +30,7 @@ def execute(self): 1- Check if setting for this feature is enabled 2- Check if workfile in work area doesn't exist 3- Check if published workfile exists and is copied locally in publish + 4- Substitute copied published workfile as first workfile Returns: None: This is a void method. From e24489c463af8ce3a83807df69af984357363bfb Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:14:58 +0100 Subject: [PATCH 16/58] comment length --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index b1b2fe2366d..d3421518239 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -129,7 +129,8 @@ def execute(self): ).format(task_name, host_name) return - # Get sync server from Tray, which handles the asynchronous thread instance + # Get sync server from Tray, + # which handles the asynchronous thread instance sync_server = next( ( t["sync_server"] From 17853d0b3b55658310bef044eb65bed19d533bed Mon Sep 17 00:00:00 2001 From: Felix David Date: Tue, 1 Nov 2022 10:50:30 +0100 Subject: [PATCH 17/58] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 4 ++-- openpype/modules/sync_server/sync_server.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index d3421518239..cf4edeac9b2 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -65,8 +65,8 @@ def execute(self): elif use_last_published_workfile is False: self.log.info( ( - 'Project "{}" has turned off to use last published workfile' - ' as first workfile for host "{}"' + 'Project "{}" has turned off to use last published' + ' workfile as first workfile for host "{}"' ).format(project_name, host_name) ) return diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index def9e6cfd8e..353b39c4e1c 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -239,7 +239,7 @@ def __init__(self, module): # Event to trigger files have been processed self.files_processed = threading.Event() - + super(SyncServerThread, self).__init__(args=(self.files_processed,)) self.module = module self.loop = None From 881bcebd1dbec626d1b1e48ebf079746ad567b0c Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 11:41:59 +0100 Subject: [PATCH 18/58] requested cosmetic changes --- .../hooks/pre_copy_last_published_workfile.py | 170 +++++++++--------- 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cf4edeac9b2..7a835507f76 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -35,6 +35,17 @@ def execute(self): Returns: None: This is a void method. """ + # Check there is no workfile available + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.log.debug( + "Last workfile exists. Skipping {} process.".format( + self.__class__.__name__ + ) + ) + return + + # Get data project_name = self.data["project_name"] task_name = self.data["task_name"] task_type = self.data["task_type"] @@ -73,97 +84,94 @@ def execute(self): self.log.info("Trying to fetch last published workfile...") - # Check there is no workfile available - last_workfile = self.data.get("last_workfile_path") - if os.path.exists(last_workfile): - self.log.debug( - "Last workfile exists. Skipping {} process.".format( - self.__class__.__name__ - ) - ) - return - project_doc = self.data.get("project_doc") asset_doc = self.data.get("asset_doc") anatomy = self.data.get("anatomy") - if project_doc and asset_doc: - # Get subset id - subset_id = next( - ( - subset["_id"] - for subset in get_subsets( - project_name, - asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], - ) - if subset["data"]["family"] == "workfile" - ), - None, - ) - if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] + + # Check it can proceed + if not project_doc and not asset_doc: + return + + # Get subset id + subset_id = next( + ( + subset["_id"] + for subset in get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "data.family"], ) - return + if subset["data"]["family"] == "workfile" + ), + None, + ) + if not subset_id: + self.log.debug('No any workfile for asset "{}".').format( + asset_doc["name"] + ) + return - # Get workfile representation - workfile_representation = next( - ( - representation - for representation in get_representations( - project_name, - version_ids=[ + # Get workfile representation + workfile_representation = next( + ( + representation + for representation in get_representations( + project_name, + version_ids=[ + ( get_last_version_by_subset_id( project_name, subset_id, fields=["_id"] - )["_id"] - ], - ) - if representation["context"]["task"]["name"] == task_name - ), - None, - ) + ) + or {} + ).get("_id") + ], + ) + if representation["context"]["task"]["name"] == task_name + ), + None, + ) - if not workfile_representation: - self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) - return + if not workfile_representation: + self.log.debug( + 'No published workfile for task "{}" and host "{}".' + ).format(task_name, host_name) + return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, - ) + # Get sync server from Tray, + # which handles the asynchronous thread instance + sync_server = next( + ( + t["sync_server"] + for t in [ + obj + for obj in gc.get_objects() + if isinstance(obj, ModulesManager) + ] + if t["sync_server"].sync_server_thread + ), + None, + ) - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, - ) - sync_server.reset_timer() + # Add site and reset timer + active_site = sync_server.get_active_site(project_name) + sync_server.add_site( + project_name, + workfile_representation["_id"], + active_site, + force=True, + ) + sync_server.reset_timer() - # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + # Wait for the download loop to end + sync_server.sync_server_thread.files_processed.wait() - # Get paths - published_workfile_path = get_representation_path( - workfile_representation, root=anatomy.roots - ) - local_workfile_dir = os.path.dirname(last_workfile) + # Get paths + published_workfile_path = get_representation_path( + workfile_representation, root=anatomy.roots + ) + local_workfile_dir = os.path.dirname(last_workfile) - # Copy file and substitute path - self.data["last_workfile_path"] = shutil.copy( - published_workfile_path, local_workfile_dir - ) + # Copy file and substitute path + self.data["last_workfile_path"] = shutil.copy( + published_workfile_path, local_workfile_dir + ) From 9e01c5deaa1615316b82d6123df8ffa1101a15ec Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:33:54 +0100 Subject: [PATCH 19/58] Change to REST API using web server --- .../hooks/pre_copy_last_published_workfile.py | 52 +++++++------- openpype/modules/sync_server/rest_api.py | 68 +++++++++++++++++++ openpype/modules/sync_server/sync_server.py | 12 ++-- .../modules/sync_server/sync_server_module.py | 9 +++ openpype/modules/timers_manager/rest_api.py | 2 +- 5 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 openpype/modules/sync_server/rest_api.py diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 7a835507f76..cefc7e5d408 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -1,14 +1,14 @@ -import gc import os import shutil +from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, get_representations, get_subsets, ) from openpype.lib import PreLaunchHook +from openpype.lib.local_settings import get_local_site_id from openpype.lib.profiles_filtering import filter_profiles -from openpype.modules.base import ModulesManager from openpype.pipeline.load.utils import get_representation_path from openpype.settings.lib import get_project_settings @@ -137,33 +137,37 @@ def execute(self): ).format(task_name, host_name) return - # Get sync server from Tray, - # which handles the asynchronous thread instance - sync_server = next( - ( - t["sync_server"] - for t in [ - obj - for obj in gc.get_objects() - if isinstance(obj, ModulesManager) - ] - if t["sync_server"].sync_server_thread - ), - None, + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return + + entry_point_url = "{}/sync_server".format(webserver_url) + rest_api_url = "{}/add_sites_to_representations".format( + entry_point_url ) + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations ('requests' is not available)" + ) + return - # Add site and reset timer - active_site = sync_server.get_active_site(project_name) - sync_server.add_site( - project_name, - workfile_representation["_id"], - active_site, - force=True, + requests.post( + rest_api_url, + json={ + "project_name": project_name, + "sites": [get_local_site_id()], + "representations": [str(workfile_representation["_id"])], + }, ) - sync_server.reset_timer() # Wait for the download loop to end - sync_server.sync_server_thread.files_processed.wait() + rest_api_url = "{}/files_are_processed".format(entry_point_url) + while requests.get(rest_api_url).content: + sleep(5) # Get paths published_workfile_path = get_representation_path( diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py new file mode 100644 index 00000000000..b7c5d26d158 --- /dev/null +++ b/openpype/modules/sync_server/rest_api.py @@ -0,0 +1,68 @@ +from aiohttp.web_response import Response +from openpype.lib import Logger + + +class SyncServerModuleRestApi: + """ + REST API endpoint used for calling from hosts when context change + happens in Workfile app. + """ + + def __init__(self, user_module, server_manager): + self._log = None + self.module = user_module + self.server_manager = server_manager + + self.prefix = "/sync_server" + + self.register() + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def register(self): + self.server_manager.add_route( + "POST", + self.prefix + "/add_sites_to_representations", + self.add_sites_to_representations, + ) + self.server_manager.add_route( + "GET", + self.prefix + "/files_are_processed", + self.files_are_processed, + ) + + async def add_sites_to_representations(self, request): + # Extract data from request + data = await request.json() + try: + project_name = data["project_name"] + sites = data["sites"] + representations = data["representations"] + except KeyError: + msg = ( + "Payload must contain fields 'project_name," + " 'sites' (list of names) and 'representations' (list of IDs)" + ) + self.log.error(msg) + return Response(status=400, message=msg) + + # Add all sites to each representation + for representation_id in representations: + for site in sites: + self.module.add_site( + project_name, representation_id, site, force=True + ) + + # Force timer to run immediately + self.module.reset_timer() + + return Response(status=200) + + async def files_are_processed(self, _request): + return Response( + body=bytes(self.module.sync_server_thread.files_are_processed) + ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 353b39c4e1c..7fd2311c2d8 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -237,15 +237,13 @@ class SyncServerThread(threading.Thread): def __init__(self, module): self.log = Logger.get_logger(self.__class__.__name__) - # Event to trigger files have been processed - self.files_processed = threading.Event() - - super(SyncServerThread, self).__init__(args=(self.files_processed,)) + super(SyncServerThread, self).__init__() self.module = module self.loop = None self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None + self.files_are_processed = False def run(self): self.is_running = True @@ -400,8 +398,8 @@ async def sync_loop(self): representation, site, error) - # Trigger files are processed - self.files_processed.set() + # Trigger files process finished + self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -460,7 +458,6 @@ async def check_shutdown(self): async def run_timer(self, delay): """Wait for 'delay' seconds to start next loop""" - self.files_processed.clear() await asyncio.sleep(delay) def reset_timer(self): @@ -469,6 +466,7 @@ def reset_timer(self): if self.timer: self.timer.cancel() self.timer = None + self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index e84c333a582..bff999723b6 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -2089,6 +2089,15 @@ def _get_roots_config(self, presets, project_name, site_name): def cli(self, click_group): click_group.add_command(cli_main) + # Webserver module implementation + def webserver_initialization(self, server_manager): + """Add routes for syncs.""" + if self.tray_initialized: + from .rest_api import SyncServerModuleRestApi + self.rest_api_obj = SyncServerModuleRestApi( + self, server_manager + ) + @click.group(SyncServerModule.name, help="SyncServer module related commands.") def cli_main(): diff --git a/openpype/modules/timers_manager/rest_api.py b/openpype/modules/timers_manager/rest_api.py index 4a2e9e65753..979db9075bd 100644 --- a/openpype/modules/timers_manager/rest_api.py +++ b/openpype/modules/timers_manager/rest_api.py @@ -21,7 +21,7 @@ def __init__(self, user_module, server_manager): @property def log(self): if self._log is None: - self._log = Logger.get_logger(self.__ckass__.__name__) + self._log = Logger.get_logger(self.__class__.__name__) return self._log def register(self): From 8d9542bb45088fec5c800fe7b7d9b76f5ca3c14c Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 3 Nov 2022 18:37:13 +0100 Subject: [PATCH 20/58] lint --- openpype/hooks/pre_copy_last_published_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index cefc7e5d408..6bec4f7d2c2 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -151,7 +151,8 @@ def execute(self): import requests except Exception: self.log.warning( - "Couldn't add sites to representations ('requests' is not available)" + "Couldn't add sites to representations " + "('requests' is not available)" ) return From 5e02d7d2d71796b6826e320fd8cfbc3e77980d93 Mon Sep 17 00:00:00 2001 From: Felix David Date: Fri, 4 Nov 2022 10:06:59 +0100 Subject: [PATCH 21/58] legacy compatibility --- openpype/hooks/pre_copy_last_published_workfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6bec4f7d2c2..f3293fa511e 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -99,9 +99,11 @@ def execute(self): for subset in get_subsets( project_name, asset_ids=[asset_doc["_id"]], - fields=["_id", "data.family"], + fields=["_id", "data.family", "data.families"], ) - if subset["data"]["family"] == "workfile" + if subset["data"].get("family") == "workfile" + # Legacy compatibility + or "workfile" in subset["data"].get("families", {}) ), None, ) From 1c48c0936290cbd7013df50d83392f53a68d51dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 16:41:19 +0100 Subject: [PATCH 22/58] use 'created_dt' of representation --- openpype/client/entities.py | 28 +++++++++++++++++++ .../hooks/pre_copy_last_published_workfile.py | 19 +++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43afccf2f14..43c2874f579 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,6 +6,7 @@ + We will need more specific functions doing wery specific queires really fast. """ +from datetime import datetime import re import collections @@ -1367,6 +1368,33 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] +def get_representation_last_created_time_on_site( + representation: dict, site_name: str +) -> datetime: + """Get `created_dt` value for representation on site. + + Args: + representation (dict): Representation to get creation date of + site_name (str): Site from which to get the creation date + + Returns: + datetime: Created time of representation on site + """ + created_time = next( + ( + site.get("created_dt") + for site in representation["files"][0].get("sites", []) + if site["name"] == site_name + ), + None, + ) + if created_time: + return created_time + else: + # Use epoch as 'zero' time + return datetime.utcfromtimestamp(0) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index f3293fa511e..4eb66f6f85e 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,6 +3,8 @@ from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, + get_representation_by_id, + get_representation_last_created_time_on_site, get_representations, get_subsets, ) @@ -158,18 +160,29 @@ def execute(self): ) return + local_site_id = get_local_site_id() requests.post( rest_api_url, json={ "project_name": project_name, - "sites": [get_local_site_id()], + "sites": [local_site_id], "representations": [str(workfile_representation["_id"])], }, ) # Wait for the download loop to end - rest_api_url = "{}/files_are_processed".format(entry_point_url) - while requests.get(rest_api_url).content: + last_created_time = get_representation_last_created_time_on_site( + workfile_representation, local_site_id + ) + while ( + last_created_time + >= get_representation_last_created_time_on_site( + get_representation_by_id( + project_name, workfile_representation["_id"] + ), + local_site_id, + ) + ): sleep(5) # Get paths From a614f0f805acd6d73c57dc68bc00a9d7834714cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:57:02 +0100 Subject: [PATCH 23/58] add priority to add_site --- openpype/modules/sync_server/rest_api.py | 17 ++++++----------- openpype/modules/sync_server/sync_server.py | 4 ---- .../modules/sync_server/sync_server_module.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index b7c5d26d158..e92ddc8eeec 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,4 +1,5 @@ from aiohttp.web_response import Response +from openpype.client.entities import get_representation_by_id from openpype.lib import Logger @@ -29,11 +30,6 @@ def register(self): self.prefix + "/add_sites_to_representations", self.add_sites_to_representations, ) - self.server_manager.add_route( - "GET", - self.prefix + "/files_are_processed", - self.files_are_processed, - ) async def add_sites_to_representations(self, request): # Extract data from request @@ -54,15 +50,14 @@ async def add_sites_to_representations(self, request): for representation_id in representations: for site in sites: self.module.add_site( - project_name, representation_id, site, force=True + project_name, + representation_id, + site, + force=True, + priority=99, ) # Force timer to run immediately self.module.reset_timer() return Response(status=200) - - async def files_are_processed(self, _request): - return Response( - body=bytes(self.module.sync_server_thread.files_are_processed) - ) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 7fd2311c2d8..d0a40a60ff1 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -243,7 +243,6 @@ def __init__(self, module): self.is_running = False self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) self.timer = None - self.files_are_processed = False def run(self): self.is_running = True @@ -398,8 +397,6 @@ async def sync_loop(self): representation, site, error) - # Trigger files process finished - self.files_are_processed = False duration = time.time() - start_time self.log.debug("One loop took {:.2f}s".format(duration)) @@ -466,7 +463,6 @@ def reset_timer(self): if self.timer: self.timer.cancel() self.timer = None - self.files_are_processed = True def _working_sites(self, project_name): if self.module.is_project_paused(project_name): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bff999723b6..6a1fc9a1c5e 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ def initialize(self, module_settings): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False): + force=False, priority=None): """ Adds new site to representation to be synced. @@ -152,6 +152,7 @@ def add_site(self, project_name, representation_id, site_name=None, representation_id (string): MongoDB _id value site_name (string): name of configured and active site force (bool): reset site if exists + priority (int): set priority Throws: SiteAlreadyPresentError - if adding already existing site and @@ -167,7 +168,8 @@ def add_site(self, project_name, representation_id, site_name=None, self.reset_site_on_representation(project_name, representation_id, site_name=site_name, - force=force) + force=force, + priority=priority) def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): @@ -1655,7 +1657,7 @@ def _get_site_rec(self, sites, site_name): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False): + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' and provider. @@ -1678,6 +1680,7 @@ def reset_site_on_representation(self, project_name, representation_id, remove (bool): if True remove site altogether pause (bool or None): if True - pause, False - unpause force (bool): hard reset - currently only for add_site + priority (int): set priority Raises: SiteAlreadyPresentError - if adding already existing site and @@ -1705,6 +1708,10 @@ def reset_site_on_representation(self, project_name, representation_id, elem = {"name": site_name} + # Add priority + if priority: + elem["priority"] = priority + if file_id: # reset site for particular file self._reset_site_for_file(project_name, representation_id, elem, file_id, site_name) From b6365d85404b88dddcb218969f0a7e30e4668e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:58:19 +0100 Subject: [PATCH 24/58] clean --- openpype/modules/sync_server/rest_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index e92ddc8eeec..0c3b9148332 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -1,5 +1,4 @@ from aiohttp.web_response import Response -from openpype.client.entities import get_representation_by_id from openpype.lib import Logger From 138051f2f4c9d0c705eeb1cd299166d0ca249850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 18:59:03 +0100 Subject: [PATCH 25/58] clean --- openpype/modules/sync_server/sync_server_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 6a1fc9a1c5e..951cb116fce 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,8 @@ def _get_site_rec(self, sites, site_name): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, priority=None): + remove=False, pause=None, force=False, + priority=None): """ Reset information about synchronization for particular 'file_id' and provider. From 3a5ebc6ea29fd4ec34b0fc80c27f5cc187ace8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:02:29 +0100 Subject: [PATCH 26/58] sort fields --- openpype/hooks/pre_copy_last_published_workfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 4eb66f6f85e..acbc9ec1c7b 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -178,7 +178,9 @@ def execute(self): last_created_time >= get_representation_last_created_time_on_site( get_representation_by_id( - project_name, workfile_representation["_id"] + project_name, + workfile_representation["_id"], + fields=["files"], ), local_site_id, ) From 29f0dee272c9b0b27c4a6e4098caab2ed11a1d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Nov 2022 19:04:03 +0100 Subject: [PATCH 27/58] clean --- openpype/modules/sync_server/sync_server_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 951cb116fce..1292bed9af0 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1657,7 +1657,7 @@ def _get_site_rec(self, sites, site_name): def reset_site_on_representation(self, project_name, representation_id, side=None, file_id=None, site_name=None, - remove=False, pause=None, force=False, + remove=False, pause=None, force=False, priority=None): """ Reset information about synchronization for particular 'file_id' From a600cf4dcad5f8caa08187ca2d449bbf9986623a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:18:23 +0100 Subject: [PATCH 28/58] fix last version check --- .../hooks/pre_copy_last_published_workfile.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index acbc9ec1c7b..96b5ccadb22 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -116,19 +116,19 @@ def execute(self): return # Get workfile representation + last_version_doc = get_last_version_by_subset_id( + project_name, subset_id, fields=["_id"] + ) + if not last_version_doc: + self.log.debug("Subset does not have any versions") + return + workfile_representation = next( ( representation for representation in get_representations( project_name, - version_ids=[ - ( - get_last_version_by_subset_id( - project_name, subset_id, fields=["_id"] - ) - or {} - ).get("_id") - ], + version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), From 7596610c160cf83b5dccb00c1638756312a54cf1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:03 +0100 Subject: [PATCH 29/58] replaced 'add_sites_to_representations' with 'reset_timer' in rest api --- openpype/modules/sync_server/rest_api.py | 31 +++--------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 0c3b9148332..51769cd4fb2 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -26,36 +26,11 @@ def log(self): def register(self): self.server_manager.add_route( "POST", - self.prefix + "/add_sites_to_representations", - self.add_sites_to_representations, + self.prefix + "/reset_timer", + self.reset_timer, ) - async def add_sites_to_representations(self, request): - # Extract data from request - data = await request.json() - try: - project_name = data["project_name"] - sites = data["sites"] - representations = data["representations"] - except KeyError: - msg = ( - "Payload must contain fields 'project_name," - " 'sites' (list of names) and 'representations' (list of IDs)" - ) - self.log.error(msg) - return Response(status=400, message=msg) - - # Add all sites to each representation - for representation_id in representations: - for site in sites: - self.module.add_site( - project_name, - representation_id, - site, - force=True, - priority=99, - ) - + async def reset_timer(self, request): # Force timer to run immediately self.module.reset_timer() From 2052afc76a72d5845570e4900e527cabe1d1ecb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:15 +0100 Subject: [PATCH 30/58] added ability to rese timer from add_site --- openpype/modules/sync_server/sync_server_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1292bed9af0..5e19a6fce07 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -136,7 +136,7 @@ def initialize(self, module_settings): """ Start of Public API """ def add_site(self, project_name, representation_id, site_name=None, - force=False, priority=None): + force=False, priority=None, reset_timer=False): """ Adds new site to representation to be synced. @@ -171,6 +171,9 @@ def add_site(self, project_name, representation_id, site_name=None, force=force, priority=priority) + if reset_timer: + self.reset_timer() + def remove_site(self, project_name, representation_id, site_name, remove_local_files=False): """ From f7c1fa01ae1b358b8af45ab777ebda4a6ba81bfc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:19:32 +0100 Subject: [PATCH 31/58] 'reset_timer' can reset timer via rest api endpoint --- .../modules/sync_server/sync_server_module.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5e19a6fce07..b505e25d2f7 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -916,7 +916,42 @@ def reset_timer(self): In case of user's involvement (reset site), start that right away. """ - self.sync_server_thread.reset_timer() + + if not self.enabled: + return + + if self.sync_server_thread is None: + self._reset_timer_with_rest_api() + else: + self.sync_server_thread.reset_timer() + + def is_representaion_on_site( + self, project_name, representation_id, site_id + ): + # TODO implement + return False + + def _reset_timer_with_rest_api(self): + # POST to webserver sites to add to representations + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") + if not webserver_url: + self.log.warning("Couldn't find webserver url") + return + + rest_api_url = "{}/sync_server/reset_timer".format( + webserver_url + ) + + try: + import requests + except Exception: + self.log.warning( + "Couldn't add sites to representations " + "('requests' is not available)" + ) + return + + requests.post(rest_api_url) def get_enabled_projects(self): """Returns list of projects which have SyncServer enabled.""" From 1d028d22ba4b796b6e8fe700b70f2f2e87217edb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:20:21 +0100 Subject: [PATCH 32/58] updated prelaunch hook with new abilities of sync server --- .../hooks/pre_copy_last_published_workfile.py | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 96b5ccadb22..6fd50a64d60 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -141,49 +141,21 @@ def execute(self): ).format(task_name, host_name) return - # POST to webserver sites to add to representations - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - if not webserver_url: - self.log.warning("Couldn't find webserver url") - return - - entry_point_url = "{}/sync_server".format(webserver_url) - rest_api_url = "{}/add_sites_to_representations".format( - entry_point_url - ) - try: - import requests - except Exception: - self.log.warning( - "Couldn't add sites to representations " - "('requests' is not available)" - ) - return - local_site_id = get_local_site_id() - requests.post( - rest_api_url, - json={ - "project_name": project_name, - "sites": [local_site_id], - "representations": [str(workfile_representation["_id"])], - }, + sync_server = self.modules_manager.get("sync_server") + sync_server.add_site( + project_name, + workfile_representation["_id"], + local_site_id, + force=True, + priority=99, + reset_timer=True ) - # Wait for the download loop to end - last_created_time = get_representation_last_created_time_on_site( - workfile_representation, local_site_id - ) - while ( - last_created_time - >= get_representation_last_created_time_on_site( - get_representation_by_id( - project_name, - workfile_representation["_id"], - fields=["files"], - ), - local_site_id, - ) + while not sync_server.is_representaion_on_site( + project_name, + workfile_representation["_id"], + local_site_id ): sleep(5) From 5db743080ccb22adc89f76fc86f4ca26020503fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:22:56 +0100 Subject: [PATCH 33/58] check if is sync server enabled --- openpype/hooks/pre_copy_last_published_workfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 6fd50a64d60..69e3d6efe40 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -37,6 +37,12 @@ def execute(self): Returns: None: This is a void method. """ + + sync_server = self.modules_manager.get("sync_server") + if not sync_server or not sync_server.enabled: + self.log.deubg("Sync server module is not enabled or available") + return + # Check there is no workfile available last_workfile = self.data.get("last_workfile_path") if os.path.exists(last_workfile): @@ -142,7 +148,6 @@ def execute(self): return local_site_id = get_local_site_id() - sync_server = self.modules_manager.get("sync_server") sync_server.add_site( project_name, workfile_representation["_id"], From 75e12954ee51d09916032224f4c72be84c12bacf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:36:03 +0100 Subject: [PATCH 34/58] removed 'get_representation_last_created_time_on_site' function --- openpype/client/entities.py | 27 ------------------- .../hooks/pre_copy_last_published_workfile.py | 2 -- 2 files changed, 29 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 43c2874f579..91d4b499b0e 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1368,33 +1368,6 @@ def get_representation_parents(project_name, representation): return parents_by_repre_id[repre_id] -def get_representation_last_created_time_on_site( - representation: dict, site_name: str -) -> datetime: - """Get `created_dt` value for representation on site. - - Args: - representation (dict): Representation to get creation date of - site_name (str): Site from which to get the creation date - - Returns: - datetime: Created time of representation on site - """ - created_time = next( - ( - site.get("created_dt") - for site in representation["files"][0].get("sites", []) - if site["name"] == site_name - ), - None, - ) - if created_time: - return created_time - else: - # Use epoch as 'zero' time - return datetime.utcfromtimestamp(0) - - def get_thumbnail_id_from_source(project_name, src_type, src_id): """Receive thumbnail id from source entity. diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 69e3d6efe40..884b0f54b62 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -3,8 +3,6 @@ from time import sleep from openpype.client.entities import ( get_last_version_by_subset_id, - get_representation_by_id, - get_representation_last_created_time_on_site, get_representations, get_subsets, ) From 99bebd82a7a3d9288799d4771a242a56dd58c40a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 9 Nov 2022 11:51:00 +0100 Subject: [PATCH 35/58] fix typo --- openpype/hooks/pre_copy_last_published_workfile.py | 2 +- openpype/modules/sync_server/sync_server_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 884b0f54b62..0e561334e1d 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -155,7 +155,7 @@ def execute(self): reset_timer=True ) - while not sync_server.is_representaion_on_site( + while not sync_server.is_representation_on_site( project_name, workfile_representation["_id"], local_site_id diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index b505e25d2f7..1f65ea9bda9 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -925,7 +925,7 @@ def reset_timer(self): else: self.sync_server_thread.reset_timer() - def is_representaion_on_site( + def is_representation_on_site( self, project_name, representation_id, site_id ): # TODO implement From 44cfbf9f2922c80c21875f6f25658e6041c6b677 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:27:07 +0100 Subject: [PATCH 36/58] added method to check if representation has all files on site --- .../modules/sync_server/sync_server_module.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1f65ea9bda9..62501465238 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,10 +926,27 @@ def reset_timer(self): self.sync_server_thread.reset_timer() def is_representation_on_site( - self, project_name, representation_id, site_id + self, project_name, representation_id, site_name ): - # TODO implement - return False + """Checks if 'representation_id' has all files avail. on 'site_name'""" + representation = get_representation_by_id(project_name, + representation_id, + fields=["_id", "files"]) + if not representation: + return False + + on_site = False + for file_info in representation.get("files", []): + for site in file_info.get("sites", []): + if site["name"] != site_name: + continue + + if (site.get("progress") or site.get("error") or + not site.get("created_dt")): + return False + on_site = True + + return on_site def _reset_timer_with_rest_api(self): # POST to webserver sites to add to representations From 2e6f850b5d9a99d7063d0693414459834a6ba373 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Nov 2022 12:48:52 +0100 Subject: [PATCH 37/58] small updates to docstrings --- openpype/modules/sync_server/sync_server_module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 62501465238..653ee50541b 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -143,7 +143,7 @@ def add_site(self, project_name, representation_id, site_name=None, 'project_name' must have synchronization enabled (globally or project only) - Used as a API endpoint from outside applications (Loader etc). + Used as an API endpoint from outside applications (Loader etc). Use 'force' to reset existing site. @@ -153,6 +153,8 @@ def add_site(self, project_name, representation_id, site_name=None, site_name (string): name of configured and active site force (bool): reset site if exists priority (int): set priority + reset_timer (bool): if delay timer should be reset, eg. user mark + some representation to be synced manually Throws: SiteAlreadyPresentError - if adding already existing site and @@ -1601,12 +1603,12 @@ def update_db(self, project_name, new_file_id, file, representation, Args: project_name (string): name of project - force to db connection as each file might come from different collection - new_file_id (string): + new_file_id (string): only present if file synced successfully file (dictionary): info about processed file (pulled from DB) representation (dictionary): parent repr of file (from DB) site (string): label ('gdrive', 'S3') error (string): exception message - progress (float): 0-1 of progress of upload/download + progress (float): 0-0.99 of progress of upload/download priority (int): 0-100 set priority Returns: From 5838f0f6097d57201402d9d5d360755f5c54b93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 9 Nov 2022 17:09:38 +0100 Subject: [PATCH 38/58] clean --- openpype/client/entities.py | 1 - openpype/modules/sync_server/rest_api.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91d4b499b0e..43afccf2f14 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -6,7 +6,6 @@ + We will need more specific functions doing wery specific queires really fast. """ -from datetime import datetime import re import collections diff --git a/openpype/modules/sync_server/rest_api.py b/openpype/modules/sync_server/rest_api.py index 51769cd4fb2..a7d9dd80b70 100644 --- a/openpype/modules/sync_server/rest_api.py +++ b/openpype/modules/sync_server/rest_api.py @@ -30,8 +30,8 @@ def register(self): self.reset_timer, ) - async def reset_timer(self, request): - # Force timer to run immediately + async def reset_timer(self, _request): + """Force timer to run immediately.""" self.module.reset_timer() return Response(status=200) From c096279cfcd9bdf5124a2e444da2830ebf300d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 15 Nov 2022 09:06:09 +0100 Subject: [PATCH 39/58] logging format --- .../hooks/pre_copy_last_published_workfile.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hooks/pre_copy_last_published_workfile.py b/openpype/hooks/pre_copy_last_published_workfile.py index 0e561334e1d..44144e5fffa 100644 --- a/openpype/hooks/pre_copy_last_published_workfile.py +++ b/openpype/hooks/pre_copy_last_published_workfile.py @@ -75,16 +75,20 @@ def execute(self): self.log.info( ( "Seems like old version of settings is used." - ' Can\'t access custom templates in host "{}".' - ).format(host_name) + ' Can\'t access custom templates in host "{}".'.format( + host_name + ) + ) ) return elif use_last_published_workfile is False: self.log.info( ( 'Project "{}" has turned off to use last published' - ' workfile as first workfile for host "{}"' - ).format(project_name, host_name) + ' workfile as first workfile for host "{}"'.format( + project_name, host_name + ) + ) ) return @@ -114,8 +118,8 @@ def execute(self): None, ) if not subset_id: - self.log.debug('No any workfile for asset "{}".').format( - asset_doc["name"] + self.log.debug( + 'No any workfile for asset "{}".'.format(asset_doc["name"]) ) return @@ -131,8 +135,7 @@ def execute(self): ( representation for representation in get_representations( - project_name, - version_ids=[last_version_doc["_id"]] + project_name, version_ids=[last_version_doc["_id"]] ) if representation["context"]["task"]["name"] == task_name ), @@ -141,8 +144,10 @@ def execute(self): if not workfile_representation: self.log.debug( - 'No published workfile for task "{}" and host "{}".' - ).format(task_name, host_name) + 'No published workfile for task "{}" and host "{}".'.format( + task_name, host_name + ) + ) return local_site_id = get_local_site_id() @@ -152,13 +157,11 @@ def execute(self): local_site_id, force=True, priority=99, - reset_timer=True + reset_timer=True, ) while not sync_server.is_representation_on_site( - project_name, - workfile_representation["_id"], - local_site_id + project_name, workfile_representation["_id"], local_site_id ): sleep(5) From 460adc767e6b81e62fdab5d2699f0c10c1023e9a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:36:44 +0800 Subject: [PATCH 40/58] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 openpype/hosts/maya/plugins/load/load_abc_to_standin.py diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py new file mode 100644 index 00000000000..defed4bd736 --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -0,0 +1,115 @@ +import os +import clique + +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.settings import get_project_settings + + +class AlembicStandinLoader(load.LoaderPlugin): + """Load Alembic as Arnold Standin""" + + families = ["model", "pointcache"] + representations = ["abc"] + + label = "Import Alembic as Standin" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, options): + + import maya.cmds as cmds + import pymel.core as pm + import mtoa.ui.arnoldmenu + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace + + version = context["version"] + version_data = version.get("data", {}) + + self.log.info("version_data: {}\n".format(version_data)) + + frameStart = version_data.get("frameStart", None) + + asset = context["asset"]["name"] + namespace = namespace or unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + #Root group + label = "{}:{}".format(namespace, name) + root = pm.group(name=label, empty=True) + + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings["maya"]["load"]["colors"] + + c = colors.get('ass') + if c is not None: + cmds.setAttr(root + ".useOutlinerColor", 1) + cmds.setAttr(root + ".outlinerColor", + c[0], c[1], c[2]) + + transform_name = label + "_ABC" + + standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) + standin = standinShape.getParent() + standin.rename(transform_name) + + pm.parent(standin, root) + + # Set the standin filepath + standinShape.dso.set(self.fname) + if frameStart is not None: + standinShape.useFrameExtension.set(1) + + nodes = [root, standin] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + import pymel.core as pm + + path = get_representation_path(representation) + + # Update the standin + standins = list() + members = pm.sets(container['objectName'], query=True) + for member in members: + shape = member.getShape() + if (shape and shape.type() == "aiStandIn"): + standins.append(shape) + + for standin in standins: + standin.dso.set(path) + standin.useFrameExtension.set(1) + + container = pm.PyNode(container["objectName"]) + container.representation.set(str(representation["_id"])) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + import maya.cmds as cmds + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From ecbe06bdc8aeee163072f6173b96ba2886b0ebb1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Oct 2022 19:56:10 +0800 Subject: [PATCH 41/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index defed4bd736..f39aa566502 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,5 +1,4 @@ import os -import clique from openpype.pipeline import ( load, @@ -41,7 +40,7 @@ def load(self, context, name, namespace, options): suffix="_", ) - #Root group + # Root group label = "{}:{}".format(namespace, name) root = pm.group(name=label, empty=True) From 2e6974b0640d0ad43bbacb2163a1cf85a7933522 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:36:58 +0800 Subject: [PATCH 42/58] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index f39aa566502..68aeb24069e 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -13,7 +13,7 @@ class AlembicStandinLoader(load.LoaderPlugin): families = ["model", "pointcache"] representations = ["abc"] - label = "Import Alembic as Standin" + label = "Import Alembic as Arnold Standin" order = -5 icon = "code-fork" color = "orange" @@ -21,7 +21,6 @@ class AlembicStandinLoader(load.LoaderPlugin): def load(self, context, name, namespace, options): import maya.cmds as cmds - import pymel.core as pm import mtoa.ui.arnoldmenu from openpype.hosts.maya.api.pipeline import containerise from openpype.hosts.maya.api.lib import unique_namespace @@ -42,7 +41,7 @@ def load(self, context, name, namespace, options): # Root group label = "{}:{}".format(namespace, name) - root = pm.group(name=label, empty=True) + root = cmds.group(name=label, empty=True) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] @@ -55,16 +54,17 @@ def load(self, context, name, namespace, options): transform_name = label + "_ABC" - standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) - standin = standinShape.getParent() - standin.rename(transform_name) + standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] + standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, children=True)[0] - pm.parent(standin, root) + cmds.parent(standin, root) # Set the standin filepath - standinShape.dso.set(self.fname) + cmds.setAttr(standinShape + ".dso", self.fname, type="string") if frameStart is not None: - standinShape.useFrameExtension.set(1) + cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] self[:] = nodes From ce5d4c02fa7f31c7731a04f5580e52f042933353 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 1 Nov 2022 20:38:51 +0800 Subject: [PATCH 43/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 68aeb24069e..5d6c52eac9f 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -55,7 +55,8 @@ def load(self, context, name, namespace, options): transform_name = label + "_ABC" standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] - standin = cmds.listRelatives(standinShape, parent=True, typ="transform") + standin = cmds.listRelatives(standinShape, parent=True, + typ="transform") standin = cmds.rename(standin, transform_name) standinShape = cmds.listRelatives(standin, children=True)[0] From c58ef40f15c405c615cb5d4ec8a566a00de5c2eb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 00:32:22 +0800 Subject: [PATCH 44/58] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 5d6c52eac9f..94bb9749173 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -10,7 +10,7 @@ class AlembicStandinLoader(load.LoaderPlugin): """Load Alembic as Arnold Standin""" - families = ["model", "pointcache"] + families = ["animation", "model", "pointcache"] representations = ["abc"] label = "Import Alembic as Arnold Standin" @@ -31,6 +31,7 @@ def load(self, context, name, namespace, options): self.log.info("version_data: {}\n".format(version_data)) frameStart = version_data.get("frameStart", None) + frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -64,7 +65,13 @@ def load(self, context, name, namespace, options): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - if frameStart is not None: + cmds.setAttr(standinShape + ".abcFPS", 25) + + if frameStart is None: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: + cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) nodes = [root, standin] @@ -93,7 +100,8 @@ def update(self, container, representation): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(1) + standin.useFrameExtension.set(0) + standin.abcFPS.set(25) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 031465779bc4096a2848a545b52c37a44a010128 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 9 Nov 2022 08:39:52 +0800 Subject: [PATCH 45/58] Alembic Loader as Arnold Standin --- .../hosts/maya/plugins/load/load_abc_to_standin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 94bb9749173..19e60d33da4 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -1,6 +1,7 @@ import os from openpype.pipeline import ( + legacy_io, load, get_representation_path ) @@ -46,6 +47,7 @@ def load(self, context, name, namespace, options): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] + fps = legacy_io.Session["AVALON_FPS"] c = colors.get('ass') if c is not None: @@ -65,12 +67,14 @@ def load(self, context, name, namespace, options): # Set the standin filepath cmds.setAttr(standinShape + ".dso", self.fname, type="string") - cmds.setAttr(standinShape + ".abcFPS", 25) + cmds.setAttr(standinShape + ".abcFPS", float(fps)) if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) + elif frameStart == 1 and frameEnd == 1: cmds.setAttr(standinShape + ".useFrameExtension", 0) + else: cmds.setAttr(standinShape + ".useFrameExtension", 1) @@ -89,7 +93,7 @@ def update(self, container, representation): import pymel.core as pm path = get_representation_path(representation) - + fps = legacy_io.Session["AVALON_FPS"] # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) @@ -101,7 +105,7 @@ def update(self, container, representation): for standin in standins: standin.dso.set(path) standin.useFrameExtension.set(0) - standin.abcFPS.set(25) + standin.abcFPS.set(float(fps)) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 6e94a81393884cab2c3b2798e2c765c08617c4d1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:09:13 +0800 Subject: [PATCH 46/58] Alembic Loader as Arnold Standin --- .../maya/plugins/load/load_abc_to_standin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 19e60d33da4..a192d9c3575 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -28,11 +28,10 @@ def load(self, context, name, namespace, options): version = context["version"] version_data = version.get("data", {}) - + family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) - frameEnd = version_data.get("frameEnd", None) asset = context["asset"]["name"] namespace = namespace or unique_namespace( @@ -48,12 +47,14 @@ def load(self, context, name, namespace, options): settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings["maya"]["load"]["colors"] fps = legacy_io.Session["AVALON_FPS"] - - c = colors.get('ass') + c = colors.get(family[0]) if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - c[0], c[1], c[2]) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" @@ -72,7 +73,7 @@ def load(self, context, name, namespace, options): if frameStart is None: cmds.setAttr(standinShape + ".useFrameExtension", 0) - elif frameStart == 1 and frameEnd == 1: + elif "model" in family: cmds.setAttr(standinShape + ".useFrameExtension", 0) else: From 66608300969101ee75cf68a8c7a86dfd7b7710d4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:10:41 +0800 Subject: [PATCH 47/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index a192d9c3575..8ce1aee3ac5 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,7 +30,6 @@ def load(self, context, name, namespace, options): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) - self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -51,10 +50,10 @@ def load(self, context, name, namespace, options): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From 7c3f625fe324328ed7e7a93e4adff4da7c1d6e8e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:11:56 +0800 Subject: [PATCH 48/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac5..d93c85f8a43 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ def load(self, context, name, namespace, options): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From bee7b17ff2b612fab87c95cdfa4143659453d049 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:13:04 +0800 Subject: [PATCH 49/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a43..dafe999d9d5 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ def load(self, context, name, namespace, options): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From 3649ee7e4e163f45472c13f5f4bb74a65175e979 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:15:11 +0800 Subject: [PATCH 50/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index dafe999d9d5..d93c85f8a43 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,9 +50,9 @@ def load(self, context, name, namespace, options): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) ) transform_name = label + "_ABC" From cd27df0e8d35ccb94a4cfde09f399cbb6319a1c4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:16:10 +0800 Subject: [PATCH 51/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index d93c85f8a43..8ce1aee3ac5 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -50,10 +50,10 @@ def load(self, context, name, namespace, options): if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + (float(c[0])/255), + (float(c[1])/255), + (float(c[2])/255) + ) transform_name = label + "_ABC" From 3186acc83e967f726ceec6c975fc74d6ea6cd8a2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 10 Nov 2022 19:19:11 +0800 Subject: [PATCH 52/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 8ce1aee3ac5..9583063c7ea 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -30,6 +30,7 @@ def load(self, context, name, namespace, options): version_data = version.get("data", {}) family = version["data"]["families"] self.log.info("version_data: {}\n".format(version_data)) + self.log.info("family: {}\n".format(family)) frameStart = version_data.get("frameStart", None) asset = context["asset"]["name"] @@ -48,12 +49,12 @@ def load(self, context, name, namespace, options): fps = legacy_io.Session["AVALON_FPS"] c = colors.get(family[0]) if c is not None: + r = (float(c[0]) / 255) + g = (float(c[1]) / 255) + b = (float(c[2]) / 255) cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + r, g, b) transform_name = label + "_ABC" From cf8bd8eb59590df3b2a196d68bbb47e29fcd862f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 14 Nov 2022 17:57:32 +0800 Subject: [PATCH 53/58] Alembic Loader as Arnold Standin --- openpype/hosts/maya/plugins/load/load_abc_to_standin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py index 9583063c7ea..605a492e4d1 100644 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py @@ -98,6 +98,7 @@ def update(self, container, representation): # Update the standin standins = list() members = pm.sets(container['objectName'], query=True) + self.log.info("container:{}".format(container)) for member in members: shape = member.getShape() if (shape and shape.type() == "aiStandIn"): @@ -105,8 +106,11 @@ def update(self, container, representation): for standin in standins: standin.dso.set(path) - standin.useFrameExtension.set(0) standin.abcFPS.set(float(fps)) + if "modelMain" in container['objectName']: + standin.useFrameExtension.set(0) + else: + standin.useFrameExtension.set(1) container = pm.PyNode(container["objectName"]) container.representation.set(str(representation["_id"])) From 93bff0c038c262290c5d8e0b5e28847c3a210777 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 16 Nov 2022 03:40:10 +0000 Subject: [PATCH 54/58] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 1953d0d6a5c..268f33083ac 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.7-nightly.4" +__version__ = "3.14.7-nightly.5" From 35b43f34ebbea13407154445369a4f8cdb15cf78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:23:26 +0000 Subject: [PATCH 55/58] Bump loader-utils from 1.4.1 to 1.4.2 in /website Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 177a4a38021..220a489dfa4 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4812,9 +4812,9 @@ loader-runner@^4.2.0: integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== loader-utils@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0" - integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" From ece1e8b9137d5a95d412b42b0e2b2fc5b4a9176a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 14:51:10 +0100 Subject: [PATCH 56/58] OP-4394 - Hound --- .../webpublisher/plugins/publish/collect_published_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index 181f8b4ab72..79ed499a20b 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -253,7 +253,7 @@ def _get_family(self, settings, task_type, is_sequence, extension): if ext: ext = ext.lower() if ext.startswith("."): - ext = ext[1:] + ext = ext[1:] lower_extensions.add(ext) # all extensions setting From 4b95ad68168b138070171c862e0afaf4c08fb9f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Nov 2022 15:57:30 +0100 Subject: [PATCH 57/58] OP-4394 - use lowercased extension in ExtractReview There might be uppercased extension sent in by accident (.PNG), which would make all checks against set of extension not work. --- openpype/plugins/publish/extract_review.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 1f9b30fba36..982bd9dc243 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -152,7 +152,7 @@ def _get_outputs_per_representations(self, instance, profile_outputs): if input_ext.startswith("."): input_ext = input_ext[1:] - if input_ext not in self.supported_exts: + if input_ext.lower() not in self.supported_exts: self.log.info( "Representation has unsupported extension \"{}\"".format( input_ext @@ -179,7 +179,7 @@ def _single_frame_filter(self, input_filepaths, output_defs): single_frame_image = False if len(input_filepaths) == 1: ext = os.path.splitext(input_filepaths[0])[-1] - single_frame_image = ext in IMAGE_EXTENSIONS + single_frame_image = ext.lower() in IMAGE_EXTENSIONS filtered_defs = [] for output_def in output_defs: @@ -501,7 +501,7 @@ def prepare_temp_data(self, instance, repre, output_def): first_sequence_frame += handle_start ext = os.path.splitext(repre["files"][0])[1].replace(".", "") - if ext in self.alpha_exts: + if ext.lower() in self.alpha_exts: input_allow_bg = True return { @@ -934,6 +934,8 @@ def input_output_paths(self, new_repre, output_def, temp_data): if output_ext.startswith("."): output_ext = output_ext[1:] + output_ext = output_ext.lower() + # Store extension to representation new_repre["ext"] = output_ext From 64e5af230a3509ef16d8c0ee0fc826284960232b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 18 Nov 2022 12:54:50 +0100 Subject: [PATCH 58/58] OP-4394 - removed explicit lower from repre ext to not shadow upper case issue Using lower here would hide possibly broken representation, as we would expect both repre["ext"] and repre["name"] be lowercased. In case the aren't review won't get created >> someone will notice and fix issues on source representation. --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 982bd9dc243..f299d1c6e92 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -152,7 +152,7 @@ def _get_outputs_per_representations(self, instance, profile_outputs): if input_ext.startswith("."): input_ext = input_ext[1:] - if input_ext.lower() not in self.supported_exts: + if input_ext not in self.supported_exts: self.log.info( "Representation has unsupported extension \"{}\"".format( input_ext