From b823f3d80e0c898c53ca8aeda2536f8f114f3243 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 26 Sep 2022 08:22:41 +0300 Subject: [PATCH 001/281] 8c4e93cfc Merge branch 'develop' into enhancement/OP-3783_Maya-Playblast-Options --- .../defaults/project_settings/maya.json | 5 ++++ .../schemas/schema_maya_capture.json | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 76ef0a7338..daaa8da8e6 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -712,6 +712,11 @@ "motionBlurEnable": false, "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.2, + "xray": false, + "udm": false, + "wos": false, + "jointXray": false, + "backfaceCulling": false, "cameras": false, "clipGhosts": false, "deformers": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 62c33f55fc..02c25e8a03 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -348,6 +348,31 @@ "type": "label", "label": "Show" }, + { + "type": "boolean", + "key": "xray", + "label": "Toggle X-Ray Mode" + }, + { + "type": "boolean", + "key": "udm", + "label": "Use Default Material" + }, + { + "type": "boolean", + "key": "wos", + "label": "Wireframe On Shaded" + }, + { + "type": "boolean", + "key": "jointXray", + "label": "Toggle Joint X-Ray Mode" + }, + { + "type": "boolean", + "key": "backfaceCulling", + "label": "Enable Back Face Culling" + }, { "type": "boolean", "key": "cameras", From 486589ef3479af1907f813b08a20c319e5f60141 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 26 Sep 2022 08:24:50 +0300 Subject: [PATCH 002/281] 8c4e93cfc Merge branch 'develop' into enhancement/OP-3783_Maya-Playblast-Options --- openpype/settings/defaults/project_anatomy/attributes.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index 983ac603f9..bf8bbef8de 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -19,8 +19,7 @@ "blender/2-91", "harmony/20", "photoshop/2021", - "aftereffects/2021", - "unreal/4-26" + "aftereffects/2021" ], "tools_env": [], "active": true From 45732d884b89b9336bf8bb4e0387ac91cc916539 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 26 Sep 2022 08:25:45 +0300 Subject: [PATCH 003/281] 8c4e93cfc Merge branch 'develop' into enhancement/OP-3783_Maya-Playblast-Options --- .../schemas/schema_maya_capture.json | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 02c25e8a03..c3d051b56e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -352,6 +352,34 @@ "type": "boolean", "key": "xray", "label": "Toggle X-Ray Mode" +<<<<<<< Updated upstream +======= + }, + { + "type": "boolean", + "key": "udm", + "label": "Use Default Material" + }, + { + "type": "boolean", + "key": "wos", + "label": "Wireframe On Shaded" + }, + { + "type": "boolean", + "key": "jointXray", + "label": "Toggle Joint X-Ray Mode" + }, + { + "type": "boolean", + "key": "backfaceCulling", + "label": "Enable Back Face Culling" + }, + { + "type": "boolean", + "key": "cameras", + "label": "cameras" +>>>>>>> Stashed changes }, { "type": "boolean", From 94cb7bee1f4b8c54e26b0364c3f272734305bba0 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:11:11 +0300 Subject: [PATCH 004/281] Move schema to a higher location. Co-authored-by: Roy Nieterau --- .../schemas/schema_maya_capture.json | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 02c25e8a03..88c994ca8f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -349,10 +349,9 @@ "label": "Show" }, { - "type": "boolean", - "key": "xray", - "label": "Toggle X-Ray Mode" - }, + "type": "label", + "label": "Shading" + }, { "type": "boolean", "key": "udm", @@ -363,15 +362,20 @@ "key": "wos", "label": "Wireframe On Shaded" }, + { + "type": "boolean", + "key": "xray", + "label": "X-Ray" + }, { "type": "boolean", "key": "jointXray", - "label": "Toggle Joint X-Ray Mode" + "label": "X-Ray Joints" }, { "type": "boolean", "key": "backfaceCulling", - "label": "Enable Back Face Culling" + "label": "Backface Culling" }, { "type": "boolean", From cfddc9f9fe2d96b88b91c9a368af2a79c0a00550 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:25:06 +0300 Subject: [PATCH 005/281] Update openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json Co-authored-by: Roy Nieterau --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 88c994ca8f..1b9ef2c30b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -354,12 +354,12 @@ }, { "type": "boolean", - "key": "udm", + "key": "useDefaultMaterial", "label": "Use Default Material" }, { "type": "boolean", - "key": "wos", + "key": "wireframeOnShaded", "label": "Wireframe On Shaded" }, { From 6e7309ea2dd077843e54456cd9b263651d73aa65 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:25:23 +0300 Subject: [PATCH 006/281] Update openpype/settings/defaults/project_settings/maya.json Co-authored-by: Roy Nieterau --- openpype/settings/defaults/project_settings/maya.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index daaa8da8e6..b873827df0 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -713,8 +713,8 @@ "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.2, "xray": false, - "udm": false, - "wos": false, + "useDefaultMaterial": false, + "wireframeOnShaded": false, "jointXray": false, "backfaceCulling": false, "cameras": false, From aeef0a484e257e868c9f5ec334e98bf4ad013f39 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 28 Sep 2022 15:06:20 +0300 Subject: [PATCH 007/281] Fix bug --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 0a28cc2552..aba6a020f8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -348,8 +348,6 @@ "type": "label", "label": "Show" }, - { - "type": "label", { "type": "boolean", "key": "useDefaultMaterial", @@ -379,7 +377,6 @@ "type": "boolean", "key": "cameras", "label": "cameras" ->>>>>>> Stashed changes }, { "type": "boolean", From 3d7af9e434e283b0606814b75589ef4643e150cd Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 28 Sep 2022 15:27:01 +0300 Subject: [PATCH 008/281] Remove redundant, move playblast options. --- .../schemas/schema_maya_capture.json | 75 +++++++------------ 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index aba6a020f8..ff3c5a79f4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -221,6 +221,31 @@ { "type": "splitter" }, + { + "type": "boolean", + "key": "useDefaultMaterial", + "label": "Use Default Material" + }, + { + "type": "boolean", + "key": "wireframeOnShaded", + "label": "Wireframe On Shaded" + }, + { + "type": "boolean", + "key": "xray", + "label": "X-Ray" + }, + { + "type": "boolean", + "key": "jointXray", + "label": "X-Ray Joints" + }, + { + "type": "boolean", + "key": "backfaceCulling", + "label": "Backface Culling" + }, { "type": "boolean", "key": "ssaoEnable", @@ -348,56 +373,6 @@ "type": "label", "label": "Show" }, - { - "type": "boolean", - "key": "useDefaultMaterial", - "label": "Use Default Material" - }, - { - "type": "boolean", - "key": "wireframeOnShaded", - "label": "Wireframe On Shaded" - }, - { - "type": "boolean", - "key": "xray", - "label": "X-Ray" - }, - { - "type": "boolean", - "key": "jointXray", - "label": "X-Ray Joints" - }, - { - "type": "boolean", - "key": "backfaceCulling", - "label": "Backface Culling" - }, - { - "type": "boolean", - "key": "cameras", - "label": "cameras" - }, - { - "type": "boolean", - "key": "udm", - "label": "Use Default Material" - }, - { - "type": "boolean", - "key": "wos", - "label": "Wireframe On Shaded" - }, - { - "type": "boolean", - "key": "jointXray", - "label": "Toggle Joint X-Ray Mode" - }, - { - "type": "boolean", - "key": "backfaceCulling", - "label": "Enable Back Face Culling" - }, { "type": "boolean", "key": "cameras", From 4d15535163119fbe2989ff068740477418eb28cf Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 3 Oct 2022 13:43:26 +0300 Subject: [PATCH 009/281] remove unnecessary line --- openpype/settings/defaults/project_anatomy/attributes.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index bf8bbef8de..c2d2aa9b15 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -18,9 +18,8 @@ "houdini/18-5", "blender/2-91", "harmony/20", - "photoshop/2021", - "aftereffects/2021" + "photoshop/2021" ], "tools_env": [], "active": true -} \ No newline at end of file +} From f6f3abf33811d875cce9a9f11829996d38645a61 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 4 Oct 2022 10:58:26 +0300 Subject: [PATCH 010/281] Add label below splitter. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ff3c5a79f4..1f0e4eeffb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -184,6 +184,10 @@ { "type": "splitter" }, + { + "type": "label", + "label": "Display" + }, { "type":"boolean", "key": "renderDepthOfField", From 3c7fa6faa5af05e96fc3a42c3ca5d5175b910301 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 4 Oct 2022 10:58:37 +0300 Subject: [PATCH 011/281] Reorder defaults --- openpype/settings/defaults/project_settings/maya.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index b873827df0..e2a3357cfe 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -695,6 +695,11 @@ "twoSidedLighting": true, "lineAAEnable": true, "multiSample": 8, + "useDefaultMaterial": false, + "wireframeOnShaded": false, + "xray": false, + "jointXray": false, + "backfaceCulling": false, "ssaoEnable": false, "ssaoAmount": 1, "ssaoRadius": 16, @@ -712,11 +717,6 @@ "motionBlurEnable": false, "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.2, - "xray": false, - "useDefaultMaterial": false, - "wireframeOnShaded": false, - "jointXray": false, - "backfaceCulling": false, "cameras": false, "clipGhosts": false, "deformers": false, From ce0a6d8ba4ba2ceae2c32cf61e4f8ec52c5f9700 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 10 Jan 2023 09:21:09 +0000 Subject: [PATCH 012/281] Fix --- openpype/hosts/maya/api/lib_renderproducts.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index c54e3ab3e0..fcc844d5d6 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -797,6 +797,11 @@ class RenderProductsVray(ARenderProducts): if default_ext in {"exr (multichannel)", "exr (deep)"}: default_ext = "exr" + # Define multipart. + multipart = False + if image_format_str == "exr (multichannel)": + multipart = True + products = [] # add beauty as default when not disabled @@ -804,23 +809,28 @@ class RenderProductsVray(ARenderProducts): if not dont_save_rgb: for camera in cameras: products.append( - RenderProduct(productName="", - ext=default_ext, - camera=camera)) + RenderProduct( + productName="", + ext=default_ext, + camera=camera, + multipart=multipart + ) + ) # separate alpha file separate_alpha = self._get_attr("vraySettings.separateAlpha") if separate_alpha: for camera in cameras: products.append( - RenderProduct(productName="Alpha", - ext=default_ext, - camera=camera) + RenderProduct( + productName="Alpha", + ext=default_ext, + camera=camera, + multipart=multipart + ) ) - - if image_format_str == "exr (multichannel)": + if multipart: # AOVs are merged in m-channel file, only main layer is rendered - self.multipart = True return products # handle aovs from references From 5bb1405a5506ceec98f885237da9a372c210e91a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 10 Jan 2023 09:21:17 +0000 Subject: [PATCH 013/281] Code cosmetics --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index b1ad3ca58e..23fcd730d5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,7 +42,6 @@ Provides: import re import os import platform -import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup From 6f404ed450f2141a2f1efba13c87918ee74948c1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 10 Jan 2023 09:21:38 +0000 Subject: [PATCH 014/281] Improve debug logging. --- .../deadline/plugins/publish/submit_publish_job.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 5c5c54febb..4d98cabe25 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -503,6 +503,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # toggle preview on if multipart is on if instance_data.get("multipartExr"): + self.log.debug("Adding preview tag because its multipartExr") preview = True self.log.debug("preview:{}".format(preview)) new_instance = deepcopy(instance_data) @@ -582,6 +583,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance["useSequenceForReview"]: # toggle preview on if multipart is on if instance.get("multipartExr", False): + self.log.debug("Adding preview tag because its multipartExr") preview = True else: render_file_name = list(collection)[0] @@ -689,8 +691,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if preview: if "ftrack" not in families: if os.environ.get("FTRACK_SERVER"): + self.log.debug( + "Adding \"ftrack\" to families because of preview tag." + ) families.append("ftrack") if "review" not in families: + self.log.debug( + "Adding \"review\" to families because of preview tag." + ) families.append("review") instance["families"] = families From a09667670a3c84afb87f95427500a9e1dc4461b5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 10 Jan 2023 09:29:23 +0000 Subject: [PATCH 015/281] Hound --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 4d98cabe25..45463ead1b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -583,7 +583,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance["useSequenceForReview"]: # toggle preview on if multipart is on if instance.get("multipartExr", False): - self.log.debug("Adding preview tag because its multipartExr") + self.log.debug( + "Adding preview tag because its multipartExr" + ) preview = True else: render_file_name = list(collection)[0] From 9c1e663cbcffc85f2a85769ca33c66a9eb71e18b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:56:47 +0000 Subject: [PATCH 016/281] Bump ua-parser-js from 0.7.31 to 0.7.33 in /website Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.31...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js 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 9af21c7500..ad80bf6915 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -7180,9 +7180,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" ua-parser-js@^0.7.30: - version "0.7.31" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" - integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + version "0.7.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" + integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== unherit@^1.0.4: version "1.1.3" From 6246bac7742aa18f852704588ba37d228f0a1413 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:29:21 +0100 Subject: [PATCH 017/281] renamed 'reset_avalon_context' to 'reset_current_context' --- openpype/pipeline/create/context.py | 6 +++--- openpype/tools/publisher/control.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9c468ae8fc..29bc32b658 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1138,7 +1138,7 @@ class CreateContext: self.reset_preparation() - self.reset_avalon_context() + self.reset_current_context() self.reset_plugins(discover_publish_plugins) self.reset_context_data() @@ -1185,8 +1185,8 @@ class CreateContext: self._collection_shared_data = None self.refresh_thumbnails() - def reset_avalon_context(self): - """Give ability to reset avalon context. + def reset_current_context(self): + """Refresh current context. Reset is based on optional host implementation of `get_current_context` function or using `legacy_io.Session`. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 50a814de5c..c11d7c53d3 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1756,7 +1756,7 @@ class PublisherController(BasePublisherController): self._create_context.reset_preparation() # Reset avalon context - self._create_context.reset_avalon_context() + self._create_context.reset_current_context() self._asset_docs_cache.reset() From d1b41ebac0b7bbac4a1404ca0233d2a7d92e6230 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:30:24 +0100 Subject: [PATCH 018/281] AvalonMongoDB is not needed for CreateContext or Controller --- openpype/pipeline/create/context.py | 38 ++++++++++++++--------------- openpype/tools/publisher/control.py | 5 ++-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 29bc32b658..e421a76b6e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1003,8 +1003,6 @@ class CreateContext: Args: host(ModuleType): Host implementation which handles implementation and global metadata. - dbcon(AvalonMongoDB): Connection to mongo with context (at least - project). headless(bool): Context is created out of UI (Current not used). reset(bool): Reset context on initialization. discover_publish_plugins(bool): Discover publish plugins during reset @@ -1012,16 +1010,8 @@ class CreateContext: """ def __init__( - self, host, dbcon=None, headless=False, reset=True, - discover_publish_plugins=True + self, host, headless=False, reset=True, discover_publish_plugins=True ): - # Create conncetion if is not passed - if dbcon is None: - session = session_data_from_environment(True) - dbcon = AvalonMongoDB(session) - dbcon.install() - - self.dbcon = dbcon self.host = host # Prepare attribute for logger (Created on demand in `log` property) @@ -1045,6 +1035,10 @@ class CreateContext: " Missing methods: {}" ).format(joined_methods)) + self._current_project_name = None + self._current_asset_name = None + self._current_task_name = None + self._host_is_valid = host_is_valid # Currently unused variable self.headless = headless @@ -1119,9 +1113,16 @@ class CreateContext: def host_name(self): return os.environ["AVALON_APP"] - @property - def project_name(self): - return self.dbcon.active_project() + def get_current_project_name(self): + return self._current_project_name + + def get_current_asset_name(self): + return self._current_asset_name + + def get_current_task_name(self): + return self._current_task_name + + project_name = property(get_current_project_name) @property def log(self): @@ -1210,12 +1211,9 @@ class CreateContext: if not task_name: task_name = legacy_io.Session.get("AVALON_TASK") - if project_name: - self.dbcon.Session["AVALON_PROJECT"] = project_name - if asset_name: - self.dbcon.Session["AVALON_ASSET"] = asset_name - if task_name: - self.dbcon.Session["AVALON_TASK"] = task_name + self._current_project_name = project_name + self._current_asset_name = asset_name + self._current_task_name = task_name def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c11d7c53d3..83c2dd4b1c 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1589,20 +1589,19 @@ class PublisherController(BasePublisherController): Handle both creation and publishing parts. Args: - dbcon (AvalonMongoDB): Connection to mongo with context. headless (bool): Headless publishing. ATM not implemented or used. """ _log = None - def __init__(self, dbcon=None, headless=False): + def __init__(self, headless=False): super(PublisherController, self).__init__() self._host = registered_host() self._headless = headless self._create_context = CreateContext( - self._host, dbcon, headless=headless, reset=False + self._host, headless=headless, reset=False ) self._publish_plugins_proxy = None From 0a900f8ae1e4ac3b1ba48aca017be03a7f743e89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:30:59 +0100 Subject: [PATCH 019/281] use 'name' attribute of host implementation if is available --- openpype/pipeline/create/context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e421a76b6e..867809a4c1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1111,6 +1111,8 @@ class CreateContext: @property def host_name(self): + if hasattr(self.host, "name"): + return self.host.name return os.environ["AVALON_APP"] def get_current_project_name(self): From 8678f4e2fa36bab16aa8033bf518dd0231fa885c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:31:27 +0100 Subject: [PATCH 020/281] 'create' returns output from creator --- openpype/pipeline/create/context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 867809a4c1..413580526e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1429,6 +1429,7 @@ class CreateContext: failed = False add_traceback = False exc_info = None + result = None try: # Fake CreatorError (Could be maybe specific exception?) if creator is None: @@ -1436,7 +1437,7 @@ class CreateContext: "Creator {} was not found".format(identifier) ) - creator.create(*args, **kwargs) + result = creator.create(*args, **kwargs) except CreatorError: failed = True @@ -1458,6 +1459,7 @@ class CreateContext: identifier, label, exc_info, add_traceback ) ]) + return result def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. From d09b7812616bfaee29c4089e2ece893ff6bd3faa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:32:53 +0100 Subject: [PATCH 021/281] implemented helper function 'create_with_context' to trigger standartized creation --- openpype/pipeline/create/context.py | 64 ++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 413580526e..655af1b8ed 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -8,17 +8,13 @@ import inspect from uuid import uuid4 from contextlib import contextmanager -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_by_name from openpype.settings import ( get_system_settings, get_project_settings ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io -from openpype.pipeline.mongodb import ( - AvalonMongoDB, - session_data_from_environment, -) from .creator_plugins import ( Creator, @@ -1461,6 +1457,64 @@ class CreateContext: ]) return result + def create_with_context( + self, + creator_identifier, + variant=None, + asset_doc=None, + task_name=None, + pre_create_data=None + ): + """Trigger create of plugins with standartized + + Args: + creator_identifier (str): + asset_doc (Dict[str, Any]): + task_name (str): Name of task to which is context related. + variant (str): Variant used for subset name. + pre_create_data (Dict[str, Any]): Pre-create attribute values. + + Returns: + Any: Output of triggered creator's 'create' method. + + Raises: + CreatorsCreateFailed: When creation fails. + """ + + if pre_create_data is None: + pre_create_data = {} + + project_name = self.project_name + if asset_doc is None: + asset_name = self.get_current_asset_name() + asset_doc = get_asset_by_name(project_name, asset_name) + task_name = self.get_current_task_name() + + creator = self.creators.get(creator_identifier) + family = None + subset_name = None + if creator is not None: + family = creator.family + subset_name = creator.get_subset_name( + variant, + task_name, + asset_doc, + project_name, + self.host_name + ) + instance_data = { + "asset": asset_doc["name"], + "task": task_name, + "variant": variant, + "family": family + } + return self.raw_create( + creator_identifier, + subset_name, + instance_data, + pre_create_data + ) + def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. From 430fe6aed42d8c08f57c7b91dd2eb9185a3d1fde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:33:33 +0100 Subject: [PATCH 022/281] renamed 'create'->'raw_create' and 'create_with_context'->'create' --- openpype/pipeline/create/context.py | 9 ++++++--- openpype/tools/publisher/control.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 655af1b8ed..a9f8ae3ce1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1407,8 +1407,8 @@ class CreateContext: with self.bulk_instances_collection(): self._bulk_instances_to_process.append(instance) - def create(self, identifier, *args, **kwargs): - """Wrapper for creators to trigger created. + def raw_create(self, identifier, *args, **kwargs): + """Wrapper for creators to trigger 'create' method. Different types of creators may expect different arguments thus the hints for args are blind. @@ -1417,6 +1417,9 @@ class CreateContext: identifier (str): Creator's identifier. *args (Tuple[Any]): Arguments for create method. **kwargs (Dict[Any, Any]): Keyword argument for create method. + + Raises: + CreatorsCreateFailed: When creation fails. """ error_message = "Failed to run Creator with identifier \"{}\". {}" @@ -1457,7 +1460,7 @@ class CreateContext: ]) return result - def create_with_context( + def create( self, creator_identifier, variant=None, diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 83c2dd4b1c..670c22a43e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2017,7 +2017,7 @@ class PublisherController(BasePublisherController): success = True try: - self._create_context.create( + self._create_context.raw_create( creator_identifier, subset_name, instance_data, options ) except CreatorsOperationFailed as exc: From 498c8564f71c4d85ad88a101d6de7ae11357bb7d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:40:08 +0100 Subject: [PATCH 023/281] swapped argments order in docstring --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a9f8ae3ce1..702731f8b2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1472,9 +1472,9 @@ class CreateContext: Args: creator_identifier (str): + variant (str): Variant used for subset name. asset_doc (Dict[str, Any]): task_name (str): Name of task to which is context related. - variant (str): Variant used for subset name. pre_create_data (Dict[str, Any]): Pre-create attribute values. Returns: From 40712089d94ce22a5d34981584dc7db8beed9554 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:48:57 +0100 Subject: [PATCH 024/281] Validate creator and asset doc --- openpype/pipeline/create/context.py | 33 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 702731f8b2..35024b5af8 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1484,27 +1484,32 @@ class CreateContext: CreatorsCreateFailed: When creation fails. """ - if pre_create_data is None: - pre_create_data = {} + creator = self.creators.get(creator_identifier) + if creator is None: + raise CreatorError( + "Creator {} was not found".format(creator_identifier) + ) project_name = self.project_name if asset_doc is None: asset_name = self.get_current_asset_name() asset_doc = get_asset_by_name(project_name, asset_name) task_name = self.get_current_task_name() + if asset_doc is None: + raise CreatorError( + "Asset with name {} was not found".format(asset_name) + ) - creator = self.creators.get(creator_identifier) - family = None - subset_name = None - if creator is not None: - family = creator.family - subset_name = creator.get_subset_name( - variant, - task_name, - asset_doc, - project_name, - self.host_name - ) + if pre_create_data is None: + pre_create_data = {} + + subset_name = creator.get_subset_name( + variant, + task_name, + asset_doc, + project_name, + self.host_name + ) instance_data = { "asset": asset_doc["name"], "task": task_name, From 75bffb4daeacc8a31dc584edf3b76b3989a0607d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:49:17 +0100 Subject: [PATCH 025/281] removed unnecessary family from instance data --- openpype/pipeline/create/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 35024b5af8..dbbde9218f 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1513,8 +1513,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, - "variant": variant, - "family": family + "variant": variant } return self.raw_create( creator_identifier, From daa961d24976a4b9a1d5f51320017fa346bbfc84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:49:27 +0100 Subject: [PATCH 026/281] variant is required argument --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index dbbde9218f..b10bbc17de 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1463,7 +1463,7 @@ class CreateContext: def create( self, creator_identifier, - variant=None, + variant, asset_doc=None, task_name=None, pre_create_data=None From 4d990e6f87964cb4f5fb2c61cebfcaea47ac3151 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 11:03:51 +0100 Subject: [PATCH 027/281] Updated docstrings --- openpype/pipeline/create/context.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b10bbc17de..190d542724 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1191,7 +1191,15 @@ class CreateContext: function or using `legacy_io.Session`. Some hosts have ability to change context file without using workfiles - tool but that change is not propagated to + tool but that change is not propagated to 'legacy_io.Session' + nor 'os.environ'. + + Todos: + UI: Current context should be also checked on save - compare + initial values vs. current values. + Related to UI checks: Current workfile can be also considered + as current context information as that's where the metadata + are stored. We should store the workfile (if is available) too. """ project_name = asset_name = task_name = None @@ -1468,12 +1476,19 @@ class CreateContext: task_name=None, pre_create_data=None ): - """Trigger create of plugins with standartized + """Trigger create of plugins with standartized arguments. + + Arguments 'asset_doc' and 'task_name' use current context as default + values. If only 'task_name' is provided it will be overriden by + task name from current context. If 'task_name' is not provided + when 'asset_doc' is, it is considered that task name is not specified, + which can lead to error if subset name template requires task name. Args: - creator_identifier (str): + creator_identifier (str): Identifier of creator plugin. variant (str): Variant used for subset name. - asset_doc (Dict[str, Any]): + asset_doc (Dict[str, Any]): Asset document which define context of + creation (possible context of created instance/s). task_name (str): Name of task to which is context related. pre_create_data (Dict[str, Any]): Pre-create attribute values. @@ -1481,6 +1496,7 @@ class CreateContext: Any: Output of triggered creator's 'create' method. Raises: + CreatorError: If creator was not found or asset is empty. CreatorsCreateFailed: When creation fails. """ From c8fb00c9c81c9a60f0436dbcb43b546060cf6664 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:43:40 +0100 Subject: [PATCH 028/281] global: expanding staging dir maker abstraction so it supports `OPENPYPE_TEMP_DIR` with anatomy formatting keys --- openpype/pipeline/publish/lib.py | 55 ++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c76671fa39..5591acf57d 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -10,7 +10,11 @@ import six import pyblish.plugin import pyblish.api -from openpype.lib import Logger, filter_profiles +from openpype.lib import ( + Logger, + filter_profiles, + StringTemplate +) from openpype.settings import ( get_project_settings, get_system_settings, @@ -623,12 +627,51 @@ def get_instance_staging_dir(instance): Returns: str: Path to staging dir of instance. """ + staging_dir = instance.data.get('stagingDir', None) + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - staging_dir = instance.data.get("stagingDir") if not staging_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data["stagingDir"] = staging_dir + custom_temp_dir = None + if openpype_temp_dir: + if "{" in openpype_temp_dir: + anatomy = instance.context.data["anatomy"] + # get anatomy formating data + # so template formating is supported + anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) + anatomy_data["root"] = anatomy.roots + """Template path formating is supporting: + - optional key formating + - available tokens: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + """ + custom_temp_dir = StringTemplate.format_template( + openpype_temp_dir, anatomy_data) + custom_temp_dir = os.path.normpath(custom_temp_dir) + # create the dir in case it doesnt exists + os.makedirs(os.path.dirname(custom_temp_dir)) + elif os.path.exists(openpype_temp_dir): + custom_temp_dir = openpype_temp_dir + + + if custom_temp_dir: + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=custom_temp_dir + ) + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + instance.context.data["cleanupFullPaths"].append(staging_dir) return staging_dir From ef86f1451542a1c9a85e9472da9ced35f6b92d95 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:51:13 +0100 Subject: [PATCH 029/281] global: update docstrings at `get_instance_staging_dir` --- openpype/pipeline/publish/lib.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 5591acf57d..cb01d4633e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -613,8 +613,21 @@ def context_plugin_should_run(plugin, context): def get_instance_staging_dir(instance): """Unified way how staging dir is stored and created on instances. - First check if 'stagingDir' is already set in instance data. If there is - not create new in tempdir. + First check if 'stagingDir' is already set in instance data. + In case there already is new tempdir will not be created. + + It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp shared + repository per project or even per more granular context. Template formating + is supported also with optional keys. Folder is created in case it doesnt exists. + + Available anatomy formating keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app Note: Staging dir does not have to be necessarily in tempdir so be carefull @@ -641,7 +654,7 @@ def get_instance_staging_dir(instance): anatomy_data["root"] = anatomy.roots """Template path formating is supporting: - optional key formating - - available tokens: + - available keys: - root[work | ] - project[name | code] - asset From 43399a08c82393c8522b3b4e7f59f16be354dbe6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:52:11 +0100 Subject: [PATCH 030/281] flame: removing class override for staging dir creation it is already available in more expanded feature at parent class --- .../publish/extract_subset_resources.py | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index d5294d61c2..c6148162a6 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -548,30 +548,3 @@ class ExtractSubsetResources(publish.Extractor): "Path `{}` is containing more that one clip".format(path) ) return clips[0] - - def staging_dir(self, instance): - """Provide a temporary directory in which to store extracted files - - Upon calling this method the staging directory is stored inside - the instance.data['stagingDir'] - """ - staging_dir = instance.data.get('stagingDir', None) - openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - - if not staging_dir: - if openpype_temp_dir and os.path.exists(openpype_temp_dir): - staging_dir = os.path.normpath( - tempfile.mkdtemp( - prefix="pyblish_tmp_", - dir=openpype_temp_dir - ) - ) - else: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data['stagingDir'] = staging_dir - - instance.context.data["cleanupFullPaths"].append(staging_dir) - - return staging_dir From 4dc9fadc424222a3f99444aaea3df8a8fd701a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 30 Jan 2023 13:56:56 +0100 Subject: [PATCH 031/281] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cb01d4633e..33f23ddb97 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -640,7 +640,9 @@ def get_instance_staging_dir(instance): Returns: str: Path to staging dir of instance. """ - staging_dir = instance.data.get('stagingDir', None) + staging_dir = instance.data.get('stagingDir') + if staging_dir: + return staging_dir openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") if not staging_dir: From a9cc08120d7f6c47b65f22b64081c73d4d5e1804 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 13:59:39 +0100 Subject: [PATCH 032/281] global: refactor code for better readibility --- openpype/pipeline/publish/lib.py | 94 +++++++++++++++++--------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 33f23ddb97..cc4304cebd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -616,11 +616,12 @@ def get_instance_staging_dir(instance): First check if 'stagingDir' is already set in instance data. In case there already is new tempdir will not be created. - It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp shared - repository per project or even per more granular context. Template formating - is supported also with optional keys. Folder is created in case it doesnt exists. + It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp + shared repository per project or even per more granular context. + Template formating is supported also with optional keys. Folder is + created in case it doesnt exists. - Available anatomy formating keys: + Available anatomy formatting keys: - root[work | ] - project[name | code] - asset @@ -643,50 +644,55 @@ def get_instance_staging_dir(instance): staging_dir = instance.data.get('stagingDir') if staging_dir: return staging_dir + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - - if not staging_dir: - custom_temp_dir = None - if openpype_temp_dir: - if "{" in openpype_temp_dir: - anatomy = instance.context.data["anatomy"] - # get anatomy formating data - # so template formating is supported - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - anatomy_data["root"] = anatomy.roots - """Template path formating is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - """ - custom_temp_dir = StringTemplate.format_template( - openpype_temp_dir, anatomy_data) - custom_temp_dir = os.path.normpath(custom_temp_dir) - # create the dir in case it doesnt exists - os.makedirs(os.path.dirname(custom_temp_dir)) - elif os.path.exists(openpype_temp_dir): - custom_temp_dir = openpype_temp_dir - - - if custom_temp_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp( - prefix="pyblish_tmp_", - dir=custom_temp_dir - ) + custom_temp_dir = None + if openpype_temp_dir: + if "{" in openpype_temp_dir: + custom_temp_dir = _formated_staging_dir( + instance, openpype_temp_dir ) - else: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") + elif os.path.exists(openpype_temp_dir): + custom_temp_dir = openpype_temp_dir + + + if custom_temp_dir: + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=custom_temp_dir ) - instance.data['stagingDir'] = staging_dir + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir instance.context.data["cleanupFullPaths"].append(staging_dir) return staging_dir + + +def _formated_staging_dir(instance, openpype_temp_dir): + anatomy = instance.context.data["anatomy"] + # get anatomy formating data + # so template formating is supported + anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) + anatomy_data["root"] = anatomy.roots + """Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + """ + result = StringTemplate.format_template(openpype_temp_dir, anatomy_data) + result = os.path.normpath(result) + # create the dir in case it doesnt exists + os.makedirs(os.path.dirname(result)) + return result From e10859d322d675a6e0945e1e9958480c9ee33ca1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 15:54:18 +0100 Subject: [PATCH 033/281] pr comments --- .../publish/extract_subset_resources.py | 3 ++ openpype/pipeline/publish/lib.py | 50 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index c6148162a6..5082217db0 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -143,6 +143,9 @@ class ExtractSubsetResources(publish.Extractor): # create staging dir path staging_dir = self.staging_dir(instance) + # append staging dir for later cleanup + instance.context.data["cleanupFullPaths"].append(staging_dir) + # add default preset type for thumbnail and reviewable video # update them with settings and override in case the same # are found in there diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cc4304cebd..a32b076775 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -616,7 +616,7 @@ def get_instance_staging_dir(instance): First check if 'stagingDir' is already set in instance data. In case there already is new tempdir will not be created. - It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp + It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. Template formating is supported also with optional keys. Folder is created in case it doesnt exists. @@ -645,17 +645,16 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") + openpype_temp_dir = os.getenv("OPENPYPE_TMPDIR") custom_temp_dir = None if openpype_temp_dir: if "{" in openpype_temp_dir: - custom_temp_dir = _formated_staging_dir( + custom_temp_dir = _format_staging_dir( instance, openpype_temp_dir ) elif os.path.exists(openpype_temp_dir): custom_temp_dir = openpype_temp_dir - if custom_temp_dir: staging_dir = os.path.normpath( tempfile.mkdtemp( @@ -669,30 +668,39 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - instance.context.data["cleanupFullPaths"].append(staging_dir) - return staging_dir -def _formated_staging_dir(instance, openpype_temp_dir): +def _format_staging_dir(instance, openpype_temp_dir): + """ Formating template + + Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + + Args: + instance (pyblish.Instance): instance object + openpype_temp_dir (str): path string + + Returns: + str: formated path + """ anatomy = instance.context.data["anatomy"] # get anatomy formating data # so template formating is supported anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) anatomy_data["root"] = anatomy.roots - """Template path formatting is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - """ - result = StringTemplate.format_template(openpype_temp_dir, anatomy_data) - result = os.path.normpath(result) - # create the dir in case it doesnt exists + + result = StringTemplate.format_template( + openpype_temp_dir, anatomy_data).normalized() + + # create the dir in case it doesnt exists os.makedirs(os.path.dirname(result)) return result From b2ed65c17ad147e44fab334534f8fd1ad837d53c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 16:06:01 +0100 Subject: [PATCH 034/281] pr comments --- openpype/pipeline/publish/lib.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index a32b076775..b3d273781e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -649,12 +649,21 @@ def get_instance_staging_dir(instance): custom_temp_dir = None if openpype_temp_dir: if "{" in openpype_temp_dir: + # path is anatomy template custom_temp_dir = _format_staging_dir( instance, openpype_temp_dir ) - elif os.path.exists(openpype_temp_dir): + else: + # path is absolute custom_temp_dir = openpype_temp_dir + if not os.path.exists(custom_temp_dir): + try: + # create it if it doesnt exists + os.makedirs(custom_temp_dir) + except IOError as error: + raise IOError("Path couldn't be created: {}".format(error)) + if custom_temp_dir: staging_dir = os.path.normpath( tempfile.mkdtemp( From 76ab705e0c33aa18b009725896d1375b5a9432a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 16:25:52 +0100 Subject: [PATCH 035/281] added documenation --- website/docs/admin_settings_system.md | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 8aeb281109..39b58e6f81 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -13,18 +13,44 @@ Settings applicable to the full studio. ![general_settings](assets/settings/settings_system_general.png) -**`Studio Name`** - Full name of the studio (can be used as variable on some places) +### Studio Name + - Full name of the studio (can be used as variable on some places) -**`Studio Code`** - Studio acronym or a short code (can be used as variable on some places) +### Studio Code + - Studio acronym or a short code (can be used as variable on some places) -**`Admin Password`** - After setting admin password, normal user won't have access to OpenPype settings +### Admin Password + - After setting admin password, normal user won't have access to OpenPype settings and Project Manager GUI. Please keep in mind that this is a studio wide password and it is meant purely as a simple barrier to prevent artists from accidental setting changes. -**`Environment`** - Globally applied environment variables that will be appended to any OpenPype process in the studio. +### Environment + - Globally applied environment variables that will be appended to any OpenPype process in the studio. + - OpenPype is using some keys to configure some tools. Here are some: -**`Disk mapping`** - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. -Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). +#### OPENPYPE_TMPDIR: + - Custom staging dir directory + - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` + - supported formating keys: + - root[work] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + +#### OPENPYPE_DEBUG + - setting logger to debug mode + - example value: "1" (to activate) + +#### OPENPYPE_LOG_LEVEL + - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) + - example value: "10" + +### Disk mapping +- Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. +- Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). ### FFmpeg and OpenImageIO tools We bundle FFmpeg tools for all platforms and OpenImageIO tools for Windows and Linux. By default, bundled tools are used, but it is possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings environments to look for them in different directory. From 280d977868d89edb3ecd15801f9bc7cf2c706d18 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Feb 2023 18:39:41 +0000 Subject: [PATCH 036/281] Only parse focussed panel if its a modelPanel. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 1f9f9db99a..e4e44e4770 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -131,8 +131,8 @@ class ExtractPlayblast(publish.Extractor): # Update preset with current panel setting # if override_viewport_options is turned off - if not override_viewport_options: - panel = cmds.getPanel(withFocus=True) + panel = cmds.getPanel(withFocus=True) or "" + if not override_viewport_options and "modelPanel" in panel: panel_preset = capture.parse_active_view() preset.update(panel_preset) cmds.setFocus(panel) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 1edafeb926..1d94bd58c5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -134,8 +134,8 @@ class ExtractThumbnail(publish.Extractor): # Update preset with current panel setting # if override_viewport_options is turned off - if not override_viewport_options: - panel = cmds.getPanel(withFocus=True) + panel = cmds.getPanel(withFocus=True) or "" + if not override_viewport_options and "modelPanel" in panel: panel_preset = capture.parse_active_view() preset.update(panel_preset) cmds.setFocus(panel) From b00aab1eed7bf92347b64ed50e44340476b671a6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Feb 2023 23:35:22 +0000 Subject: [PATCH 037/281] Fix rounding --- openpype/hosts/maya/api/lib.py | 149 +++++++++++++----- .../plugins/publish/validate_maya_units.py | 11 +- 2 files changed, 115 insertions(+), 45 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index e5fa883c99..887c04d257 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1969,8 +1969,6 @@ def get_id_from_sibling(node, history_only=True): return first_id - -# Project settings def set_scene_fps(fps, update=True): """Set FPS from project configuration @@ -1983,28 +1981,21 @@ def set_scene_fps(fps, update=True): """ - fps_mapping = {'15': 'game', - '24': 'film', - '25': 'pal', - '30': 'ntsc', - '48': 'show', - '50': 'palf', - '60': 'ntscf', - '23.98': '23.976fps', - '23.976': '23.976fps', - '29.97': '29.97fps', - '47.952': '47.952fps', - '47.95': '47.952fps', - '59.94': '59.94fps', - '44100': '44100fps', - '48000': '48000fps'} - - # pull from mapping - # this should convert float string to float and int to int - # so 25.0 is converted to 25, but 23.98 will be still float. - dec, ipart = math.modf(fps) - if dec == 0.0: - fps = int(ipart) + fps_mapping = { + '15': 'game', + '24': 'film', + '25': 'pal', + '30': 'ntsc', + '48': 'show', + '50': 'palf', + '60': 'ntscf', + '23.976023976023978': '23.976fps', + '29.97002997002997': '29.97fps', + '47.952047952047955': '47.952fps', + '59.94005994005994': '59.94fps', + '44100': '44100fps', + '48000': '48000fps' + } unit = fps_mapping.get(str(fps), None) if unit is None: @@ -2124,7 +2115,9 @@ def set_context_settings(): asset_data = asset_doc.get("data", {}) # Set project fps - fps = asset_data.get("fps", project_data.get("fps", 25)) + fps = convert_to_maya_fps( + asset_data.get("fps", project_data.get("fps", 25)) + ) legacy_io.Session["AVALON_FPS"] = str(fps) set_scene_fps(fps) @@ -2146,15 +2139,12 @@ def validate_fps(): """ - fps = get_current_project_asset(fields=["data.fps"])["data"]["fps"] - # TODO(antirotor): This is hack as for framerates having multiple - # decimal places. FTrack is ceiling decimal values on - # fps to two decimal places but Maya 2019+ is reporting those fps - # with much higher resolution. As we currently cannot fix Ftrack - # rounding, we have to round those numbers coming from Maya. - current_fps = float_round(mel.eval('currentTimeUnitToFPS()'), 2) + expected_fps = convert_to_maya_fps( + get_current_project_asset(fields=["data.fps"])["data"]["fps"] + ) + current_fps = mel.eval('currentTimeUnitToFPS()') - fps_match = current_fps == fps + fps_match = current_fps == expected_fps if not fps_match and not IS_HEADLESS: from openpype.widgets import popup @@ -2163,14 +2153,19 @@ def validate_fps(): dialog = popup.PopupUpdateKeys(parent=parent) dialog.setModal(True) dialog.setWindowTitle("Maya scene does not match project FPS") - dialog.setMessage("Scene %i FPS does not match project %i FPS" % - (current_fps, fps)) + dialog.setMessage( + "Scene {} FPS does not match project {} FPS".format( + current_fps, expected_fps + ) + ) dialog.setButtonText("Fix") # Set new text for button (add optional argument for the popup?) toggle = dialog.widgets["toggle"] update = toggle.isChecked() - dialog.on_clicked_state.connect(lambda: set_scene_fps(fps, update)) + dialog.on_clicked_state.connect( + lambda: set_scene_fps(expected_fps, update) + ) dialog.show() @@ -3353,3 +3348,85 @@ def iter_visible_nodes_in_range(nodes, start, end): def get_attribute_input(attr): connections = cmds.listConnections(attr, plugs=True, destination=False) return connections[0] if connections else None + + +def convert_to_maya_fps(fps): + """Convert any fps to supported Maya framerates.""" + float_framerates = [ + 23.976023976023978, + # WTF is 29.97 df vs fps? + 29.97002997002997, + 47.952047952047955, + 59.94005994005994 + ] + # 44100 fps evaluates as 41000.0. Why? Omitting for now. + int_framerates = [ + 2, + 3, + 4, + 5, + 6, + 8, + 10, + 12, + 15, + 16, + 20, + 24, + 25, + 30, + 40, + 48, + 50, + 60, + 75, + 80, + 90, + 100, + 120, + 125, + 150, + 200, + 240, + 250, + 300, + 375, + 400, + 500, + 600, + 750, + 1200, + 1500, + 2000, + 3000, + 6000, + 48000 + ] + + # If input fps is a whole number we'll return. + if float(fps).is_integer(): + # Validate fps is part of Maya's fps selection. + if fps not in int_framerates: + raise ValueError( + "Framerate \"{}\" is not supported in Maya".format(fps) + ) + return fps + else: + # Differences to supported float frame rates. + differences = [] + for i in float_framerates: + differences.append(abs(i - fps)) + + # Validate difference does not stray too far from supported framerates. + min_difference = min(differences) + min_index = differences.index(min_difference) + supported_framerate = float_framerates[min_index] + if round(min_difference) != 0: + raise ValueError( + "Framerate \"{}\" strays too far from any supported framerate" + " in Maya. Closest supported framerate is \"{}\"".format( + fps, supported_framerate + ) + ) + + return supported_framerate diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index e6fabb1712..ad256b6a72 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -33,18 +33,11 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): linearunits = context.data.get('linearUnits') angularunits = context.data.get('angularUnits') - # TODO(antirotor): This is hack as for framerates having multiple - # decimal places. FTrack is ceiling decimal values on - # fps to two decimal places but Maya 2019+ is reporting those fps - # with much higher resolution. As we currently cannot fix Ftrack - # rounding, we have to round those numbers coming from Maya. - # NOTE: this must be revisited yet again as it seems that Ftrack is - # now flooring the value? - fps = mayalib.float_round(context.data.get('fps'), 2, ceil) + fps = context.data.get('fps') # TODO repace query with using 'context.data["assetEntity"]' asset_doc = get_current_project_asset() - asset_fps = asset_doc["data"]["fps"] + asset_fps = mayalib.convert_to_maya_fps(asset_doc["data"]["fps"]) self.log.info('Units (linear): {0}'.format(linearunits)) self.log.info('Units (angular): {0}'.format(angularunits)) From 9b6bd7954d99640db5391cce39e87d02dc0e557a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 6 Feb 2023 15:44:34 +0000 Subject: [PATCH 038/281] Working AssStandinLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 119 +++++++++---------- 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 5db6fc3dfa..6317c0a7ce 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -1,6 +1,9 @@ import os import clique +import maya.cmds as cmds +import mtoa.ui.arnoldmenu + from openpype.settings import get_project_settings from openpype.pipeline import ( load, @@ -15,6 +18,15 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api.pipeline import containerise +def is_sequence(files): + sequence = False + collections, remainder = clique.assemble(files) + if collections: + sequence = True + + return sequence + + class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load Arnold Proxy as reference""" @@ -27,16 +39,12 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): color = "orange" def process_reference(self, context, name, namespace, options): - - import maya.cmds as cmds - import pymel.core as pm - version = context['version'] version_data = version.get("data", {}) self.log.info("version_data: {}\n".format(version_data)) - frameStart = version_data.get("frameStart", None) + frame_start = version_data.get("frame_start", None) try: family = context["representation"]["context"]["family"] @@ -49,7 +57,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = self.fname proxyPath_base = os.path.splitext(path)[0] - if frameStart is not None: + if frame_start is not None: proxyPath_base = os.path.splitext(proxyPath_base)[0] publish_folder = os.path.split(path)[0] @@ -63,11 +71,13 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) - proxyPath = proxyPath_base + ".ma" + proxyPath = proxyPath_base + ".ass" project_name = context["project"]["name"] - file_url = self.prepare_root_value(proxyPath, - project_name) + file_url = self.prepare_root_value( + proxyPath, project_name + ) + self.log.info(file_url) nodes = cmds.file(file_url, namespace=namespace, @@ -80,7 +90,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): translate=True, scale=True) # Set attributes - proxyShape = pm.ls(nodes, type="mesh")[0] + proxyShape = cmds.ls(nodes, type="mesh")[0] proxyShape.aiTranslator.set('procedural') proxyShape.dso.set(path) @@ -92,10 +102,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): c = colors.get(family) if c is not None: cmds.setAttr(groupName + ".useOutlinerColor", 1) - cmds.setAttr(groupName + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + cmds.setAttr( + groupName + ".outlinerColor", + (float(c[0]) / 255), + (float(c[1]) / 255), + (float(c[2]) / 255) ) self[:] = nodes @@ -106,18 +117,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): - from maya import cmds - import pymel.core as pm - node = container["objectName"] representation["context"].pop("frame", None) path = get_representation_path(representation) - print(path) - # path = self.fname - print(self.fname) proxyPath = os.path.splitext(path)[0] + ".ma" - print(proxyPath) # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) @@ -186,18 +190,11 @@ class AssStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): - - import maya.cmds as cmds - import mtoa.ui.arnoldmenu - import pymel.core as pm - 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 + "_", @@ -205,36 +202,34 @@ class AssStandinLoader(load.LoaderPlugin): suffix="_", ) - # cmds.loadPlugin("gpuCache", quiet=True) - # 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'] - c = colors.get('ass') - if c is not None: - cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - c[0], c[1], c[2]) + color = colors.get('ass') + if color is not None: + cmds.setAttr(root + ".useOutlinerColor", True) + cmds.setAttr( + root + ".outlinerColor", color[0], color[1], color[2] + ) # Create transform with shape transform_name = label + "_ASS" - # transform = pm.createNode("transform", name=transform_name, - # parent=root) - standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) - standin = standinShape.getParent() - standin.rename(transform_name) + standinShape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standinShape, parent=True)[0] + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, shapes=True)[0] - pm.parent(standin, root) + cmds.parent(standin, root) # Set the standin filepath - standinShape.dso.set(self.fname) - if frameStart is not None: - standinShape.useFrameExtension.set(1) + cmds.setAttr(standinShape + ".dso", self.fname, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) + cmds.setAttr(standinShape + ".useFrameExtension", sequence) nodes = [root, standin] self[:] = nodes @@ -247,31 +242,27 @@ class AssStandinLoader(load.LoaderPlugin): loader=self.__class__.__name__) def update(self, container, representation): - - import pymel.core as pm - - path = get_representation_path(representation) - - files_in_path = os.listdir(os.path.split(path)[0]) - sequence = 0 - collections, remainder = clique.assemble(files_in_path) - if collections: - sequence = 1 - # Update the standin standins = list() - members = pm.sets(container['objectName'], query=True) + members = cmds.sets(container['objectName'], query=True) for member in members: - shape = member.getShape() - if (shape and shape.type() == "aiStandIn"): - standins.append(shape) + shapes = cmds.listRelatives(member, shapes=True) + if not shapes: + continue + if cmds.nodeType(shapes[0]) == "aiStandIn": + standins.append(shapes[0]) + path = get_representation_path(representation) + sequence = is_sequence(os.listdir(os.path.dirname(path))) for standin in standins: - standin.dso.set(path) - standin.useFrameExtension.set(sequence) + cmds.setAttr(standin + ".dso", path, type="string") + cmds.setAttr(standin + ".useFrameExtension", sequence) - container = pm.PyNode(container["objectName"]) - container.representation.set(str(representation["_id"])) + cmds.setAttr( + container["objectName"] + ".representation", + str(representation["_id"]), + type="string" + ) def switch(self, container, representation): self.update(container, representation) From 23987420a375c199b095b295282728e913bde75a Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 6 Feb 2023 19:03:06 +0100 Subject: [PATCH 039/281] update asset info of imported sets --- .../maya/api/workfile_template_builder.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index ef043ed0f4..56a53c070c 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -2,7 +2,7 @@ import json from maya import cmds -from openpype.pipeline import registered_host +from openpype.pipeline import registered_host, legacy_io from openpype.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, AbstractTemplateBuilder, @@ -41,10 +41,26 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): )) cmds.sets(name=PLACEHOLDER_SET, empty=True) - cmds.file(path, i=True, returnNewNodes=True) + new_nodes = cmds.file(path, i=True, returnNewNodes=True) cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True) + imported_sets = cmds.ls(new_nodes, set=True) + if not imported_sets: + return True + + # update imported sets information + for node in imported_sets: + if not cmds.attributeQuery("id", node=node, exists=True): + continue + if cmds.getAttr("{}.id".format(node)) != "pyblish.avalon.instance": + continue + if not cmds.attributeQuery("asset", node=node, exists=True): + continue + asset = legacy_io.Session["AVALON_ASSET"] + + cmds.setAttr("{}.asset".format(node), asset, type="string") + return True From 3e25f2cddac284ed4583f751687525f1e2471e4a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 6 Feb 2023 18:08:25 +0000 Subject: [PATCH 040/281] Working AssProxyLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 97 +++++++++----------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 6317c0a7ce..ada65998a5 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -27,6 +27,18 @@ def is_sequence(files): return sequence +def set_color(node, context): + project_name = context["project"]["name"] + settings = get_project_settings(project_name) + colors = settings['maya']['load']['colors'] + color = colors.get('ass') + if color is not None: + cmds.setAttr(node + ".useOutlinerColor", True) + cmds.setAttr( + node + ".outlinerColor", color[0], color[1], color[2] + ) + + class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load Arnold Proxy as reference""" @@ -46,19 +58,14 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): frame_start = version_data.get("frame_start", None) - try: - family = context["representation"]["context"]["family"] - except ValueError: - family = "ass" - with maintained_selection(): groupName = "{}:{}".format(namespace, name) path = self.fname - proxyPath_base = os.path.splitext(path)[0] + proxy_path_base = os.path.splitext(path)[0] if frame_start is not None: - proxyPath_base = os.path.splitext(proxyPath_base)[0] + proxy_path_base = os.path.splitext(proxy_path_base)[0] publish_folder = os.path.split(path)[0] files_in_folder = os.listdir(publish_folder) @@ -71,43 +78,33 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) - proxyPath = proxyPath_base + ".ass" + proxy_path = proxy_path_base + ".ma" + msg = proxy_path + " does not exist." + assert os.path.exists(proxy_path), msg - project_name = context["project"]["name"] - file_url = self.prepare_root_value( - proxyPath, project_name + nodes = cmds.file( + proxy_path, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=groupName ) - self.log.info(file_url) - nodes = cmds.file(file_url, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName=groupName) - - cmds.makeIdentity(groupName, apply=False, rotate=True, - translate=True, scale=True) + cmds.makeIdentity( + groupName, apply=False, rotate=True, translate=True, scale=True + ) # Set attributes - proxyShape = cmds.ls(nodes, type="mesh")[0] + proxy_shape = cmds.ls(nodes, type="mesh")[0] - proxyShape.aiTranslator.set('procedural') - proxyShape.dso.set(path) - proxyShape.aiOverrideShaders.set(0) + cmds.setAttr( + proxy_shape + ".aiTranslator", "procedural", type="string" + ) + cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") + cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - if c is not None: - cmds.setAttr(groupName + ".useOutlinerColor", 1) - cmds.setAttr( - groupName + ".outlinerColor", - (float(c[0]) / 255), - (float(c[1]) / 255), - (float(c[2]) / 255) - ) + set_color(groupName, context) self[:] = nodes @@ -121,16 +118,16 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): representation["context"].pop("frame", None) path = get_representation_path(representation) - proxyPath = os.path.splitext(path)[0] + ".ma" + proxy_path = os.path.splitext(path)[0] + ".ma" # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) reference_node = get_reference_node(members) - assert os.path.exists(proxyPath), "%s does not exist." % proxyPath + assert os.path.exists(proxy_path), "%s does not exist." % proxy_path try: - file_url = self.prepare_root_value(proxyPath, + file_url = self.prepare_root_value(proxy_path, representation["context"] ["project"] ["name"]) @@ -140,11 +137,13 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True) # Set attributes - proxyShape = pm.ls(content, type="mesh")[0] + proxy_shape = cmds.ls(content, type="mesh")[0] - proxyShape.aiTranslator.set('procedural') - proxyShape.dso.set(path) - proxyShape.aiOverrideShaders.set(0) + cmds.setAttr( + proxy_shape + ".aiTranslator", "procedural", type="string" + ) + cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") + cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) except RuntimeError as exc: # When changing a reference to a file that has load errors the @@ -206,15 +205,7 @@ class AssStandinLoader(load.LoaderPlugin): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - settings = get_project_settings(os.environ['AVALON_PROJECT']) - colors = settings['maya']['load']['colors'] - - color = colors.get('ass') - if color is not None: - cmds.setAttr(root + ".useOutlinerColor", True) - cmds.setAttr( - root + ".outlinerColor", color[0], color[1], color[2] - ) + set_color(root, context) # Create transform with shape transform_name = label + "_ASS" From ecef4cbb4691ff7ea8320fda55e3ac4a8d70c4f2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 08:57:10 +0000 Subject: [PATCH 041/281] More information about issues with publishing. --- openpype/hosts/maya/plugins/load/load_ass.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index ada65998a5..e4e0b0da84 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -79,7 +79,10 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) proxy_path = proxy_path_base + ".ma" - msg = proxy_path + " does not exist." + msg = ( + proxy_path + " does not exist.\nThere are most likely no " + + "proxy shapes in the \"proxy_SET\" when publishing." + ) assert os.path.exists(proxy_path), msg nodes = cmds.file( From 662d68daec5c0b786489e7035bd75be77a2cdd48 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 10:06:56 +0000 Subject: [PATCH 042/281] Support for multiple proxy meshes. --- openpype/hosts/maya/plugins/load/load_ass.py | 39 +++++++++++++++++-- .../hosts/maya/plugins/publish/collect_ass.py | 4 -- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index e4e0b0da84..58abfa964e 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -60,7 +60,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): with maintained_selection(): - groupName = "{}:{}".format(namespace, name) + group_name = "{}:{}".format(namespace, name) path = self.fname proxy_path_base = os.path.splitext(path)[0] @@ -91,13 +91,36 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): reference=True, returnNewNodes=True, groupReference=True, - groupName=groupName + groupName=group_name ) + # Reset group to world zero. + transform_data = {} + for node in nodes: + if cmds.nodeType(node) != "transform": + continue + + transform_data[node] = {} + attrs = ["translate", "rotate", "scale"] + parameters = ["X", "Y", "Z"] + for attr in attrs: + for parameter in parameters: + transform_data[node][attr + parameter] = cmds.getAttr( + "{}.{}{}".format(node, attr, parameter) + ) + cmds.makeIdentity( - groupName, apply=False, rotate=True, translate=True, scale=True + group_name, + apply=False, + rotate=True, + translate=True, + scale=True ) + for node, data in transform_data.items(): + for attr, value in data.items(): + cmds.setAttr("{}.{}".format(node, attr), value) + # Set attributes proxy_shape = cmds.ls(nodes, type="mesh")[0] @@ -107,7 +130,15 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - set_color(groupName, context) + # Hides all other meshes at render time. + remaining_meshes = cmds.ls(nodes, type="mesh") + remaining_meshes.remove(proxy_shape) + for node in remaining_meshes: + cmds.setAttr( + node + ".aiTranslator", "procedural", type="string" + ) + + set_color(group_name, context) self[:] = nodes diff --git a/openpype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_ass.py index b5e05d6665..7b5d1a00c7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_ass.py +++ b/openpype/hosts/maya/plugins/publish/collect_ass.py @@ -1,5 +1,4 @@ from maya import cmds -from openpype.pipeline.publish import KnownPublishError import pyblish.api @@ -25,9 +24,6 @@ class CollectAssData(pyblish.api.InstancePlugin): instance.data['setMembers'] = members self.log.debug('content members: {}'.format(members)) elif objset.startswith("proxy_SET"): - if len(members) != 1: - msg = "You have multiple proxy meshes, please only use one" - raise KnownPublishError(msg) instance.data['proxy'] = members self.log.debug('proxy members: {}'.format(members)) From 77a6139c777d25b7bfd05eff55af94184de716ab Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 10:13:22 +0000 Subject: [PATCH 043/281] Fix updating proxy --- openpype/hosts/maya/plugins/load/load_ass.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 58abfa964e..59cfae7cdb 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -148,14 +148,14 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): - node = container["objectName"] + container_node = container["objectName"] representation["context"].pop("frame", None) path = get_representation_path(representation) proxy_path = os.path.splitext(path)[0] + ".ma" # Get reference node from container members - members = cmds.sets(node, query=True, nodesOnly=True) + members = cmds.sets(container_node, query=True, nodesOnly=True) reference_node = get_reference_node(members) assert os.path.exists(proxy_path), "%s does not exist." % proxy_path @@ -195,18 +195,26 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.log.warning("Ignoring file read error:\n%s", exc) + # Hides all other meshes at render time. + remaining_meshes = cmds.ls(content, type="mesh") + remaining_meshes.remove(proxy_shape) + for node in remaining_meshes: + cmds.setAttr( + node + ".aiTranslator", "procedural", type="string" + ) + # Add new nodes of the reference to the container - cmds.sets(content, forceElement=node) + cmds.sets(content, forceElement=container_node) # Remove any placeHolderList attribute entries from the set that # are remaining from nodes being removed from the referenced file. - members = cmds.sets(node, query=True) + members = cmds.sets(container_node, query=True) invalid = [x for x in members if ".placeHolderList" in x] if invalid: - cmds.sets(invalid, remove=node) + cmds.sets(invalid, remove=container_node) # Update metadata - cmds.setAttr("{}.representation".format(node), + cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), type="string") From 9733b07f6dd383a63c00d51312bf700a475aa5d8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 15:21:58 +0000 Subject: [PATCH 044/281] Remove AssProxyLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 195 +------------------ 1 file changed, 5 insertions(+), 190 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 59cfae7cdb..50b72d87e8 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -9,12 +9,7 @@ from openpype.pipeline import ( load, get_representation_path ) -import openpype.hosts.maya.api.plugin -from openpype.hosts.maya.api.plugin import get_reference_node -from openpype.hosts.maya.api.lib import ( - maintained_selection, - unique_namespace -) +from openpype.hosts.maya.api.lib import unique_namespace from openpype.hosts.maya.api.pipeline import containerise @@ -39,193 +34,13 @@ def set_color(node, context): ) -class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Load Arnold Proxy as reference""" +class ArnoldStandinLoader(load.LoaderPlugin): + """Load file as Arnold standin""" families = ["ass"] representations = ["ass"] - label = "Reference .ASS standin with Proxy" - order = -10 - icon = "code-fork" - color = "orange" - - def process_reference(self, context, name, namespace, options): - version = context['version'] - version_data = version.get("data", {}) - - self.log.info("version_data: {}\n".format(version_data)) - - frame_start = version_data.get("frame_start", None) - - with maintained_selection(): - - group_name = "{}:{}".format(namespace, name) - path = self.fname - proxy_path_base = os.path.splitext(path)[0] - - if frame_start is not None: - proxy_path_base = os.path.splitext(proxy_path_base)[0] - - publish_folder = os.path.split(path)[0] - files_in_folder = os.listdir(publish_folder) - collections, remainder = clique.assemble(files_in_folder) - - if collections: - hashes = collections[0].padding * '#' - coll = collections[0].format('{head}[index]{tail}') - filename = coll.replace('[index]', hashes) - - path = os.path.join(publish_folder, filename) - - proxy_path = proxy_path_base + ".ma" - msg = ( - proxy_path + " does not exist.\nThere are most likely no " + - "proxy shapes in the \"proxy_SET\" when publishing." - ) - assert os.path.exists(proxy_path), msg - - nodes = cmds.file( - proxy_path, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName=group_name - ) - - # Reset group to world zero. - transform_data = {} - for node in nodes: - if cmds.nodeType(node) != "transform": - continue - - transform_data[node] = {} - attrs = ["translate", "rotate", "scale"] - parameters = ["X", "Y", "Z"] - for attr in attrs: - for parameter in parameters: - transform_data[node][attr + parameter] = cmds.getAttr( - "{}.{}{}".format(node, attr, parameter) - ) - - cmds.makeIdentity( - group_name, - apply=False, - rotate=True, - translate=True, - scale=True - ) - - for node, data in transform_data.items(): - for attr, value in data.items(): - cmds.setAttr("{}.{}".format(node, attr), value) - - # Set attributes - proxy_shape = cmds.ls(nodes, type="mesh")[0] - - cmds.setAttr( - proxy_shape + ".aiTranslator", "procedural", type="string" - ) - cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") - cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - - # Hides all other meshes at render time. - remaining_meshes = cmds.ls(nodes, type="mesh") - remaining_meshes.remove(proxy_shape) - for node in remaining_meshes: - cmds.setAttr( - node + ".aiTranslator", "procedural", type="string" - ) - - set_color(group_name, context) - - self[:] = nodes - - return nodes - - def switch(self, container, representation): - self.update(container, representation) - - def update(self, container, representation): - container_node = container["objectName"] - - representation["context"].pop("frame", None) - path = get_representation_path(representation) - proxy_path = os.path.splitext(path)[0] + ".ma" - - # Get reference node from container members - members = cmds.sets(container_node, query=True, nodesOnly=True) - reference_node = get_reference_node(members) - - assert os.path.exists(proxy_path), "%s does not exist." % proxy_path - - try: - file_url = self.prepare_root_value(proxy_path, - representation["context"] - ["project"] - ["name"]) - content = cmds.file(file_url, - loadReference=reference_node, - type="mayaAscii", - returnNewNodes=True) - - # Set attributes - proxy_shape = cmds.ls(content, type="mesh")[0] - - cmds.setAttr( - proxy_shape + ".aiTranslator", "procedural", type="string" - ) - cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") - cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - - except RuntimeError as exc: - # When changing a reference to a file that has load errors the - # command will raise an error even if the file is still loaded - # correctly (e.g. when raising errors on Arnold attributes) - # When the file is loaded and has content, we consider it's fine. - if not cmds.referenceQuery(reference_node, isLoaded=True): - raise - - content = cmds.referenceQuery(reference_node, - nodes=True, - dagPath=True) - if not content: - raise - - self.log.warning("Ignoring file read error:\n%s", exc) - - # Hides all other meshes at render time. - remaining_meshes = cmds.ls(content, type="mesh") - remaining_meshes.remove(proxy_shape) - for node in remaining_meshes: - cmds.setAttr( - node + ".aiTranslator", "procedural", type="string" - ) - - # Add new nodes of the reference to the container - cmds.sets(content, forceElement=container_node) - - # Remove any placeHolderList attribute entries from the set that - # are remaining from nodes being removed from the referenced file. - members = cmds.sets(container_node, query=True) - invalid = [x for x in members if ".placeHolderList" in x] - if invalid: - cmds.sets(invalid, remove=container_node) - - # Update metadata - cmds.setAttr("{}.representation".format(container_node), - str(representation["_id"]), - type="string") - - -class AssStandinLoader(load.LoaderPlugin): - """Load .ASS file as standin""" - - families = ["ass"] - representations = ["ass"] - - label = "Load .ASS file as standin" + label = "Load file as Arnold standin" order = -5 icon = "code-fork" color = "orange" @@ -250,7 +65,7 @@ class AssStandinLoader(load.LoaderPlugin): set_color(root, context) # Create transform with shape - transform_name = label + "_ASS" + transform_name = label + "_standin" standinShape = mtoa.ui.arnoldmenu.createStandIn() standin = cmds.listRelatives(standinShape, parent=True)[0] From 01d763fe991f1ecdc83b1ce8b6ee002beead7dea Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 16:29:02 +0000 Subject: [PATCH 045/281] Extract Ass proxy. --- .../hosts/maya/plugins/publish/extract_ass.py | 87 ++++++++++++++----- .../maya/plugins/publish/extract_assproxy.py | 81 ----------------- 2 files changed, 63 insertions(+), 105 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_assproxy.py diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 049f256a7a..4cff9d0183 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,16 +1,18 @@ import os +import copy from maya import cmds import arnold from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.lib import StringTemplate -class ExtractAssStandin(publish.Extractor): - """Extract the content of the instance to a ass file""" +class ExtractArnoldSceneSource(publish.Extractor): + """Extract the content of the instance to an Arnold Scene Source file.""" - label = "Arnold Scene Source (.ass)" + label = "Arnold Scene Source" hosts = ["maya"] families = ["ass"] asciiAss = False @@ -18,7 +20,6 @@ class ExtractAssStandin(publish.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) filename = "{}.ass".format(instance.name) - filenames = [] file_path = os.path.join(staging_dir, filename) # Mask @@ -42,7 +43,7 @@ class ExtractAssStandin(publish.Extractor): mask = mask ^ node_types[key] # Motion blur - values = { + attribute_data = { "defaultArnoldRenderOptions.motion_blur_enable": instance.data.get( "motionBlur", True ), @@ -70,13 +71,65 @@ class ExtractAssStandin(publish.Extractor): "mask": mask } - self.log.info("Writing: '%s'" % file_path) - with attribute_values(values): + filenames = self._extract( + instance.data["setMembers"], attribute_data, kwargs + ) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "ass", + "ext": "ass", + "files": filenames if len(filenames) > 1 else filenames[0], + "stagingDir": staging_dir, + "frameStart": kwargs["startFrame"] + } + + instance.data["representations"].append(representation) + + self.log.info( + "Extracted instance {} to: {}".format(instance.name, staging_dir) + ) + + # Extract proxy. + kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") + filenames = self._extract( + instance.data["proxy"], attribute_data, kwargs + ) + + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "ass"}) + templates = instance.context.data["anatomy"].templates["publish"] + published_filename_without_extension = StringTemplate( + templates["file"] + ).format(template_data).replace(".ass", "_proxy") + transfers = [] + for filename in filenames: + source = os.path.join(staging_dir, filename) + destination = os.path.join( + instance.data["resourcesDir"], + filename.replace( + filename.split(".")[0], + published_filename_without_extension + ) + ) + transfers.append((source, destination)) + + for source, destination in transfers: + self.log.debug("Transfer: {} > {}".format(source, destination)) + + instance.data["transfers"] = transfers + + def _extract(self, nodes, attribute_data, kwargs): + self.log.info("Writing: " + kwargs["filename"]) + filenames = [] + with attribute_values(attribute_data): with maintained_selection(): self.log.info( - "Writing: {}".format(instance.data["setMembers"]) + "Writing: {}".format(nodes) ) - cmds.select(instance.data["setMembers"], noExpand=True) + cmds.select(nodes, noExpand=True) self.log.info( "Extracting ass sequence with: {}".format(kwargs) @@ -89,18 +142,4 @@ class ExtractAssStandin(publish.Extractor): self.log.info("Exported: {}".format(filenames)) - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'ass', - 'ext': 'ass', - 'files': filenames if len(filenames) > 1 else filenames[0], - "stagingDir": staging_dir, - 'frameStart': kwargs["startFrame"] - } - - instance.data["representations"].append(representation) - - self.log.info("Extracted instance '%s' to: %s" - % (instance.name, staging_dir)) + return filenames diff --git a/openpype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py deleted file mode 100644 index 4937a28a9e..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_assproxy.py +++ /dev/null @@ -1,81 +0,0 @@ -import os -import contextlib - -from maya import cmds - -from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection - - -class ExtractAssProxy(publish.Extractor): - """Extract proxy model as Maya Ascii to use as arnold standin - - - """ - - order = publish.Extractor.order + 0.2 - label = "Ass Proxy (Maya ASCII)" - hosts = ["maya"] - families = ["ass"] - - def process(self, instance): - - @contextlib.contextmanager - def unparent(root): - """Temporarily unparent `root`""" - parent = cmds.listRelatives(root, parent=True) - if parent: - cmds.parent(root, world=True) - yield - self.log.info("{} - {}".format(root, parent)) - cmds.parent(root, parent) - else: - yield - - # Define extract output file path - stagingdir = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(stagingdir, filename) - - # Perform extraction - self.log.info("Performing extraction..") - - # Get only the shape contents we need in such a way that we avoid - # taking along intermediateObjects - proxy = instance.data.get('proxy', None) - - if not proxy: - self.log.info("no proxy mesh") - return - - members = cmds.ls(proxy, - dag=True, - transforms=True, - noIntermediate=True) - self.log.info(members) - - with maintained_selection(): - with unparent(members[0]): - cmds.select(members, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=False, - constraints=False, - expressions=False, - constructionHistory=False) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'ma', - 'ext': 'ma', - 'files': filename, - "stagingDir": stagingdir - } - instance.data["representations"].append(representation) - - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) From 1d6206d41456df1dec2f60617ba54390d1857dfd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 16:39:57 +0000 Subject: [PATCH 046/281] Rename plugin --- .../maya/plugins/load/{load_ass.py => load_arnold_standin.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/maya/plugins/load/{load_ass.py => load_arnold_standin.py} (100%) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py similarity index 100% rename from openpype/hosts/maya/plugins/load/load_ass.py rename to openpype/hosts/maya/plugins/load/load_arnold_standin.py From e3c58662b9cf05211d35c5282e5d25a2fb5b46f0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:13:25 +0000 Subject: [PATCH 047/281] Working loading proxy --- .../maya/plugins/load/load_arnold_standin.py | 117 ++++++++++++++---- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 50b72d87e8..57e1d8a6e0 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -9,7 +9,9 @@ from openpype.pipeline import ( load, get_representation_path ) -from openpype.hosts.maya.api.lib import unique_namespace +from openpype.hosts.maya.api.lib import ( + unique_namespace, get_attribute_input, maintained_selection +) from openpype.hosts.maya.api.pipeline import containerise @@ -22,18 +24,6 @@ def is_sequence(files): return sequence -def set_color(node, context): - project_name = context["project"]["name"] - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - color = colors.get('ass') - if color is not None: - cmds.setAttr(node + ".useOutlinerColor", True) - cmds.setAttr( - node + ".outlinerColor", color[0], color[1], color[2] - ) - - class ArnoldStandinLoader(load.LoaderPlugin): """Load file as Arnold standin""" @@ -62,24 +52,35 @@ class ArnoldStandinLoader(load.LoaderPlugin): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - set_color(root, context) + # Set color. + project_name = context["project"]["name"] + settings = get_project_settings(project_name) + colors = settings['maya']['load']['colors'] + color = colors.get('ass') + if color is not None: + cmds.setAttr(root + ".useOutlinerColor", True) + cmds.setAttr( + root + ".outlinerColor", color[0], color[1], color[2] + ) - # Create transform with shape - transform_name = label + "_standin" + with maintained_selection(): + # Create transform with shape + transform_name = label + "_standin" - standinShape = mtoa.ui.arnoldmenu.createStandIn() - standin = cmds.listRelatives(standinShape, parent=True)[0] - standin = cmds.rename(standin, transform_name) - standinShape = cmds.listRelatives(standin, shapes=True)[0] + standinShape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standinShape, parent=True)[0] + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, shapes=True)[0] - cmds.parent(standin, root) + cmds.parent(standin, root) - # Set the standin filepath - cmds.setAttr(standinShape + ".dso", self.fname, type="string") - sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) - cmds.setAttr(standinShape + ".useFrameExtension", sequence) + # Set the standin filepath + dso_path, operator = self._setup_proxy(standinShape, self.fname) + cmds.setAttr(standinShape + ".dso", dso_path, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) + cmds.setAttr(standinShape + ".useFrameExtension", sequence) - nodes = [root, standin] + nodes = [root, standin, operator] self[:] = nodes return containerise( @@ -89,6 +90,70 @@ class ArnoldStandinLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) + def get_next_free_multi_index(self, attr_name): + """Find the next unconnected multi index at the input attribute.""" + + start_index = 0 + # Assume a max of 10 million connections + while start_index < 10000000: + connection_info = cmds.connectionInfo( + "{}[{}]".format(attr_name, start_index), + sourceFromDestination=True + ) + if len(connection_info or []) == 0: + return start_index + start_index += 1 + + def _setup_proxy(self, shape, path): + basename_split = os.path.basename(path).split(".") + proxy_basename = ( + basename_split[0] + "_proxy." + ".".join(basename_split[1:]) + ) + proxy_path = "/".join( + [os.path.dirname(path), "resources", proxy_basename] + ) + + if not os.path.exists(proxy_path): + self.log.error("Proxy files do not exist. Skipping proxy setup.") + return path + + options_node = "defaultArnoldRenderOptions" + merge_operator = get_attribute_input(options_node + ".operator") + if merge_operator is None: + merge_operator = cmds.createNode("aiMerge") + cmds.connectAttr( + merge_operator + ".message", options_node + ".operator" + ) + + merge_operator = merge_operator.split(".")[0] + + string_replace_operator = cmds.createNode("aiStringReplace") + cmds.setAttr( + string_replace_operator + ".selection", + "*.(@node=='procedural')", + type="string" + ) + cmds.setAttr( + string_replace_operator + ".match", + "resources/" + proxy_basename, + type="string" + ) + cmds.setAttr( + string_replace_operator + ".replace", + os.path.basename(path), + type="string" + ) + + cmds.connectAttr( + string_replace_operator + ".out", + "{}.inputs[{}]".format( + merge_operator, + self.get_next_free_multi_index(merge_operator + ".inputs") + ) + ) + + return proxy_path, string_replace_operator + def update(self, container, representation): # Update the standin standins = list() From af752ae60663cd64f038ab154216a52e61d6dcb2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:23:04 +0000 Subject: [PATCH 048/281] Working proxy update --- .../maya/plugins/load/load_arnold_standin.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 57e1d8a6e0..a90aa02d4d 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -104,7 +104,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): return start_index start_index += 1 - def _setup_proxy(self, shape, path): + def _get_proxy_path(self, path): basename_split = os.path.basename(path).split(".") proxy_basename = ( basename_split[0] + "_proxy." + ".".join(basename_split[1:]) @@ -112,10 +112,14 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_path = "/".join( [os.path.dirname(path), "resources", proxy_basename] ) + return proxy_basename, proxy_path + + def _setup_proxy(self, shape, path): + proxy_basename, proxy_path = self._get_proxy_path(path) if not os.path.exists(proxy_path): self.log.error("Proxy files do not exist. Skipping proxy setup.") - return path + return os.path.basename(path), path options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") @@ -156,20 +160,33 @@ class ArnoldStandinLoader(load.LoaderPlugin): def update(self, container, representation): # Update the standin - standins = list() members = cmds.sets(container['objectName'], query=True) for member in members: + if cmds.nodeType(member) == "aiStringReplace": + string_replace_operator = member + shapes = cmds.listRelatives(member, shapes=True) if not shapes: continue if cmds.nodeType(shapes[0]) == "aiStandIn": - standins.append(shapes[0]) + standin = shapes[0] path = get_representation_path(representation) + proxy_basename, proxy_path = self._get_proxy_path(path) + cmds.setAttr( + string_replace_operator + ".match", + "resources/" + proxy_basename, + type="string" + ) + cmds.setAttr( + string_replace_operator + ".replace", + os.path.basename(path), + type="string" + ) + cmds.setAttr(standin + ".dso", proxy_path, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(path))) - for standin in standins: - cmds.setAttr(standin + ".dso", path, type="string") - cmds.setAttr(standin + ".useFrameExtension", sequence) + cmds.setAttr(standin + ".useFrameExtension", sequence) cmds.setAttr( container["objectName"] + ".representation", From bb4a44fe33d0a6e1518179f19efbf309969a3d28 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:30:34 +0000 Subject: [PATCH 049/281] Clean up string replace operator --- .../hosts/maya/plugins/load/load_arnold_standin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index a90aa02d4d..635e86708b 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -75,7 +75,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.parent(standin, root) # Set the standin filepath - dso_path, operator = self._setup_proxy(standinShape, self.fname) + dso_path, operator = self._setup_proxy( + standinShape, self.fname, namespace + ) cmds.setAttr(standinShape + ".dso", dso_path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standinShape + ".useFrameExtension", sequence) @@ -114,7 +116,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) return proxy_basename, proxy_path - def _setup_proxy(self, shape, path): + def _setup_proxy(self, shape, path, namespace): proxy_basename, proxy_path = self._get_proxy_path(path) if not os.path.exists(proxy_path): @@ -131,7 +133,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): merge_operator = merge_operator.split(".")[0] - string_replace_operator = cmds.createNode("aiStringReplace") + string_replace_operator = cmds.createNode( + "aiStringReplace", name=namespace + ":string_replace_operator" + ) cmds.setAttr( string_replace_operator + ".selection", "*.(@node=='procedural')", @@ -198,7 +202,6 @@ class ArnoldStandinLoader(load.LoaderPlugin): 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) From 87712716d047716800bf27a934a9648b9024bed5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 07:48:45 +0000 Subject: [PATCH 050/281] Combine Alembic standin --- .../maya/plugins/load/load_abc_to_standin.py | 132 ------------------ .../maya/plugins/load/load_arnold_standin.py | 18 +-- 2 files changed, 10 insertions(+), 140 deletions(-) delete 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 deleted file mode 100644 index 70866a3ba6..0000000000 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ /dev/null @@ -1,132 +0,0 @@ -import os - -from openpype.pipeline import ( - legacy_io, - load, - get_representation_path -) -from openpype.settings import get_project_settings - - -class AlembicStandinLoader(load.LoaderPlugin): - """Load Alembic as Arnold Standin""" - - families = ["animation", "model", "proxyAbc", "pointcache"] - representations = ["abc"] - - label = "Import Alembic as Arnold Standin" - order = -5 - icon = "code-fork" - color = "orange" - - def load(self, context, name, namespace, options): - - import maya.cmds as cmds - 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", {}) - 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"] - namespace = namespace or unique_namespace( - asset + "_", - prefix="_" if asset[0].isdigit() else "", - suffix="_", - ) - - # Root group - label = "{}:{}".format(namespace, name) - root = cmds.group(name=label, empty=True) - - settings = get_project_settings(os.environ['AVALON_PROJECT']) - colors = settings["maya"]["load"]["colors"] - 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", - r, g, b) - - transform_name = label + "_ABC" - - 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] - - cmds.parent(standin, root) - - # Set the standin filepath - cmds.setAttr(standinShape + ".dso", self.fname, type="string") - cmds.setAttr(standinShape + ".abcFPS", float(fps)) - - if frameStart is None: - cmds.setAttr(standinShape + ".useFrameExtension", 0) - - elif "model" in family: - cmds.setAttr(standinShape + ".useFrameExtension", 0) - - else: - cmds.setAttr(standinShape + ".useFrameExtension", 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) - fps = legacy_io.Session["AVALON_FPS"] - # 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"): - standins.append(shape) - - for standin in standins: - standin.dso.set(path) - 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"])) - - 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 diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 635e86708b..3cfc5b71b3 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -25,12 +25,12 @@ def is_sequence(files): class ArnoldStandinLoader(load.LoaderPlugin): - """Load file as Arnold standin""" + """Load as Arnold standin""" - families = ["ass"] - representations = ["ass"] + families = ["ass", "animation", "model", "proxyAbc", "pointcache"] + representations = ["ass", "abc"] - label = "Load file as Arnold standin" + label = "Load as Arnold standin" order = -5 icon = "code-fork" color = "orange" @@ -75,14 +75,16 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.parent(standin, root) # Set the standin filepath - dso_path, operator = self._setup_proxy( + path, operator = self._setup_proxy( standinShape, self.fname, namespace ) - cmds.setAttr(standinShape + ".dso", dso_path, type="string") + cmds.setAttr(standinShape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standinShape + ".useFrameExtension", sequence) - nodes = [root, standin, operator] + nodes = [root, standin] + if operator is not None: + nodes.append(operator) self[:] = nodes return containerise( @@ -121,7 +123,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): if not os.path.exists(proxy_path): self.log.error("Proxy files do not exist. Skipping proxy setup.") - return os.path.basename(path), path + return path, None options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") From 067a6c7c93ba3b55debf0a6deae39218871bee76 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:19:11 +0000 Subject: [PATCH 051/281] Code cosmetics --- ...ect_ass.py => collect_arnold_scene_source.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename openpype/hosts/maya/plugins/publish/{collect_ass.py => collect_arnold_scene_source.py} (72%) diff --git a/openpype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py similarity index 72% rename from openpype/hosts/maya/plugins/publish/collect_ass.py rename to openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 7b5d1a00c7..06d0786665 100644 --- a/openpype/hosts/maya/plugins/publish/collect_ass.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -3,16 +3,16 @@ from maya import cmds import pyblish.api -class CollectAssData(pyblish.api.InstancePlugin): - """Collect Ass data.""" +class CollectArnoldSceneSource(pyblish.api.InstancePlugin): + """Collect Arnold Scene Source data.""" # Offset to be after renderable camera collection. order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Ass' + label = "Collect Arnold Scene Source" families = ["ass"] def process(self, instance): - objsets = instance.data['setMembers'] + objsets = instance.data["setMembers"] for objset in objsets: objset = str(objset) @@ -21,11 +21,11 @@ class CollectAssData(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if "content_SET" in objset: - instance.data['setMembers'] = members - self.log.debug('content members: {}'.format(members)) + instance.data["setMembers"] = members + self.log.debug("content members: {}".format(members)) elif objset.startswith("proxy_SET"): - instance.data['proxy'] = members - self.log.debug('proxy members: {}'.format(members)) + instance.data["proxy"] = members + self.log.debug("proxy members: {}".format(members)) # Use camera in object set if present else default to render globals # camera. From 7d5ede8ae51e5de7d22c8b6dfd3270638502dd98 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:27:10 +0000 Subject: [PATCH 052/281] Fix creating multiple ass instances --- .../{create_ass.py => create_arnold_scene_source.py} | 10 +++++----- .../plugins/publish/collect_arnold_scene_source.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename openpype/hosts/maya/plugins/create/{create_ass.py => create_arnold_scene_source.py} (84%) diff --git a/openpype/hosts/maya/plugins/create/create_ass.py b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py similarity index 84% rename from openpype/hosts/maya/plugins/create/create_ass.py rename to openpype/hosts/maya/plugins/create/create_arnold_scene_source.py index 935a068ca5..2afb897e94 100644 --- a/openpype/hosts/maya/plugins/create/create_ass.py +++ b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py @@ -6,7 +6,7 @@ from openpype.hosts.maya.api import ( from maya import cmds -class CreateAss(plugin.Creator): +class CreateArnoldSceneSource(plugin.Creator): """Arnold Scene Source""" name = "ass" @@ -29,7 +29,7 @@ class CreateAss(plugin.Creator): maskOperator = False def __init__(self, *args, **kwargs): - super(CreateAss, self).__init__(*args, **kwargs) + super(CreateArnoldSceneSource, self).__init__(*args, **kwargs) # Add animation data self.data.update(lib.collect_animation_data()) @@ -52,7 +52,7 @@ class CreateAss(plugin.Creator): self.data["maskOperator"] = self.maskOperator def process(self): - instance = super(CreateAss, self).process() + instance = super(CreateArnoldSceneSource, self).process() nodes = [] @@ -61,6 +61,6 @@ class CreateAss(plugin.Creator): cmds.sets(nodes, rm=instance) - assContent = cmds.sets(name="content_SET") - assProxy = cmds.sets(name="proxy_SET", empty=True) + assContent = cmds.sets(name=instance + "_content_SET") + assProxy = cmds.sets(name=instance + "_proxy_SET", empty=True) cmds.sets([assContent, assProxy], forceElement=instance) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 06d0786665..c0275eef7b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -20,10 +20,10 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): if members is None: self.log.warning("Skipped empty instance: \"%s\" " % objset) continue - if "content_SET" in objset: + if objset.endswith("content_SET"): instance.data["setMembers"] = members self.log.debug("content members: {}".format(members)) - elif objset.startswith("proxy_SET"): + elif objset.endswith("proxy_SET"): instance.data["proxy"] = members self.log.debug("proxy members: {}".format(members)) From 33f2168e785206bd6eb3096fa52789f6cc7d737f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:27:17 +0000 Subject: [PATCH 053/281] Code cosmetics --- .../publish/{extract_ass.py => extract_arnold_scene_source.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/maya/plugins/publish/{extract_ass.py => extract_arnold_scene_source.py} (100%) diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py similarity index 100% rename from openpype/hosts/maya/plugins/publish/extract_ass.py rename to openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py From f03cb52538b048167ce5a6f994953094b58d88c5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 09:40:25 +0000 Subject: [PATCH 054/281] Proxy workflow for pointcache --- .../maya/plugins/create/create_pointcache.py | 8 +++ .../maya/plugins/load/load_arnold_standin.py | 3 +- .../plugins/publish/collect_pointcache.py | 30 ++++++++++ .../plugins/publish/extract_pointcache.py | 55 +++++++++++++++++-- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index cdec140ea8..63c0490dc7 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -1,3 +1,5 @@ +from maya import cmds + from openpype.hosts.maya.api import ( lib, plugin @@ -37,3 +39,9 @@ class CreatePointCache(plugin.Creator): # Default to not send to farm. self.data["farm"] = False self.data["priority"] = 50 + + def process(self): + instance = super(CreatePointCache, self).process() + + assProxy = cmds.sets(name=instance + "_proxy_SET", empty=True) + cmds.sets(assProxy, forceElement=instance) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 3cfc5b71b3..e2bb89ed77 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -138,9 +138,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): string_replace_operator = cmds.createNode( "aiStringReplace", name=namespace + ":string_replace_operator" ) + node_type = "alembic" if path.endswith(".abc") else "procedural" cmds.setAttr( string_replace_operator + ".selection", - "*.(@node=='procedural')", + "*.(@node=='{}')".format(node_type), type="string" ) cmds.setAttr( diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index a841341f72..332992ca92 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -1,3 +1,5 @@ +from maya import cmds + import pyblish.api @@ -12,3 +14,31 @@ class CollectPointcache(pyblish.api.InstancePlugin): def process(self, instance): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + + proxy_set = None + for node in instance.data["setMembers"]: + if cmds.nodeType(node) != "objectSet": + continue + members = cmds.sets(node, query=True) + if members is None: + self.log.warning("Skipped empty objectset: \"%s\" " % node) + continue + if node.endswith("proxy_SET"): + proxy_set = node + instance.data["proxy"] = [] + instance.data["proxyRoots"] = [] + for member in members: + instance.data["proxy"].extend(cmds.ls(member, long=True)) + instance.data["proxyRoots"].extend( + cmds.ls(member, long=True) + ) + instance.data["proxy"].extend( + cmds.listRelatives(member, shapes=True, fullPath=True) + ) + self.log.debug( + "proxy members: {}".format(instance.data["proxy"]) + ) + + if proxy_set: + instance.remove(proxy_set) + instance.data["setMembers"].remove(proxy_set) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7ed73fd5b0..0eb65e4226 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -1,4 +1,5 @@ import os +import copy from maya import cmds @@ -9,6 +10,7 @@ from openpype.hosts.maya.api.lib import ( maintained_selection, iter_visible_nodes_in_range ) +from openpype.lib import StringTemplate class ExtractAlembic(publish.Extractor): @@ -23,9 +25,7 @@ class ExtractAlembic(publish.Extractor): label = "Extract Pointcache (Alembic)" hosts = ["maya"] - families = ["pointcache", - "model", - "vrayproxy"] + families = ["pointcache", "model", "vrayproxy"] targets = ["local", "remote"] def process(self, instance): @@ -87,6 +87,7 @@ class ExtractAlembic(publish.Extractor): end=end)) suspend = not instance.data.get("refresh", False) + self.log.info(nodes) with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) @@ -101,9 +102,9 @@ class ExtractAlembic(publish.Extractor): instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": dirname } instance.data["representations"].append(representation) @@ -112,6 +113,48 @@ class ExtractAlembic(publish.Extractor): self.log.info("Extracted {} to {}".format(instance, dirname)) + # Extract proxy. + if not instance.data.get("proxy"): + return + + path = path.replace(".abc", "_proxy.abc") + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = instance.data["proxyRoots"] + + with suspended_refresh(suspend=suspend): + with maintained_selection(): + cmds.select(instance.data["proxy"]) + extract_alembic( + file=path, + startFrame=start, + endFrame=end, + **options + ) + + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "abc"}) + templates = instance.context.data["anatomy"].templates["publish"] + published_filename_without_extension = StringTemplate( + templates["file"] + ).format(template_data).replace(".abc", "_proxy") + transfers = [] + destination = os.path.join( + instance.data["resourcesDir"], + filename.replace( + filename.split(".")[0], + published_filename_without_extension + ) + ) + transfers.append((path, destination)) + + for source, destination in transfers: + self.log.debug("Transfer: {} > {}".format(source, destination)) + + instance.data["transfers"] = transfers + def get_members_and_roots(self, instance): return instance[:], instance.data.get("setMembers") From 713ede50049b3df473fb66d2ce8e556bce46df5c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 10:11:10 +0000 Subject: [PATCH 055/281] Documentation --- website/docs/artist_hosts_maya.md | 3 +++ website/docs/artist_hosts_maya_arnold.md | 16 ++++++++++++++++ website/docs/assets/maya-pointcache_setup.png | Bin 49860 -> 88602 bytes website/sidebars.js | 1 + 4 files changed, 20 insertions(+) create mode 100644 website/docs/artist_hosts_maya_arnold.md diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 14619e52a1..9fab845e62 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -308,6 +308,8 @@ Select its root and Go **OpenPype → Create...** and select **Point Cache**. After that, publishing will create corresponding **abc** files. +When creating the instance, a objectset child `proxy` will be created. Meshes in the `proxy` objectset will be the viewport representation where loading supports proxies. Proxy representations are stored as `resources` of the subset. + Example setup: ![Maya - Point Cache Example](assets/maya-pointcache_setup.png) @@ -315,6 +317,7 @@ Example setup: :::note Publish on farm If your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle `Farm` property in created pointcache instance to True. +::: ### Loading Point Caches diff --git a/website/docs/artist_hosts_maya_arnold.md b/website/docs/artist_hosts_maya_arnold.md new file mode 100644 index 0000000000..b8b8da6d57 --- /dev/null +++ b/website/docs/artist_hosts_maya_arnold.md @@ -0,0 +1,16 @@ +--- +id: artist_hosts_maya_arnold +title: Arnold for Maya +sidebar_label: Arnold +--- +## Arnold Scene Source (.ass) +Arnold Scene Source can be published as a single file or a sequence of files, determined by the frame range. + +When creating the instance, two objectsets are created; `content` and `proxy`. Meshes in the `proxy` objectset will be the viewport representation when loading as `standin`. Proxy representations are stored as `resources` of the subset. + +## Standin +Arnold Scene Source `ass` and Alembic `abc` are supported to load as standins. + +If a subset has a proxy representation, this will be used as display in the viewport. At render time the standin path will be replaced using the recommended string replacement workflow; + +https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_maya_operators_am_Updating_procedural_file_paths_with_string_replace_html diff --git a/website/docs/assets/maya-pointcache_setup.png b/website/docs/assets/maya-pointcache_setup.png index 8904baa239f90f342f7252f5732ccf57dccf2ad7..b2dc12690199c264e6a8ceebbbff4308e08764be 100644 GIT binary patch literal 88602 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%0DJ*m$sy4TvzuBkclL5Ev|*snH)H zei-|gVo8E`U*9RLGU2RGQ%&6x~ISb8ZL(NlLl{NP!DmypL>1#D3 zo}?K82DZR<2Uhqrs(|3{c0G+i&ez&O?!<;lgUk8BA$Esh41uo7&?S}>@z{_*lI_pd zzg@?ECDf#9e+71pb8J2Ztf##eynNw&IMR2c&i8UAfv4%xB+#Z+ytrdBa}YnG;NEX8 z=7Ar1QRh~C%<}Q`;)W@oB`)_DVAQCxMz~Rs}VEX$4Qh z^KrVr)SD}-sDba=z;3HY(yQBPKoh`zdTUDHB^no3G9)!2BM_ckL)J@M{mPGXta6}w zKQym30P-W_L44)Qvs`Pk`X;+gA4`yT@;WrgJ7b+yvjXC+NNT5yDi)1=Nd*+b`K$Gan?6c*fDR##ZDV z`#sF;aox0Y=M^{Oyv?!3&unC9+{bjm}OB)wUV~SA3m)r^1COKr!nZg{r58lQkpTb^|SEK;S!m;{Fd;Qn?XM& zgYT-^52~kMM+fl0KdXA|x3ca_zG+v{kN{+5iF10ug3N zvG%tS=8owb`!49tvA#4!Rx-G_+G>S|eEaGx2QouYE9TlYB5J1WaD`KCXh??_8-aXSGL!$pGRY8u~?4-lhXkeWOc96K@589en>% z<7V0)fr|*)GZe}OTUqv=zaF_ZrjHoG?z#HwqhdQjQE6{qP;eLqoEMd>DTI(Xwnkg6F%(G%QDB>VrXDR@T(nDg z4Dcpu9p{Q`R`Ouxdn2mrl~YSuek8lu)8gVc+LG5)G4TtuyV0lMJh3&CB({S zVO+9zP7+Yeo~_Rap=>qV4MttcY<;$S!N_N))!7TPm81#`oX2*VfN7=Vb(S%o&QVpM zQ>t+Niv=)V{4yH0J94ldeW4yb8=S^->1n9X%scat?LOO$BT<=sfc(Z*bmbnaue7wM z4Ae5puA+{!)x>OWFj~}IFzIi{N<7m zz_%tTR^9k6{!nLypOoiOUa-eFaDV-EROE)SUk(t5)bx!rxHmdqAE6}mkiMqUGD2h$8JAlylO-^Q@fs0xi~1SBw4pE;~BgVS5v$AWdVr)`eR{Z zaRmQIYzRq*(kW^8&MWS#RbjA$2#FldA(_3JD-nMY$*B&Q3u#skfBaM+TN3N<5&CVk zhk}AT6gjJ$akz(Sr}Oqk1t-(m2;rXQQn(?gmk(+qfpHwCUY*mG1b`Kg>fV$8 zUmn->?Qy5)(jed2ovY%Fej!Ey<#FAMZd&TmB)NjXjmJQ|?ksAS5f7>Ja|8cwg_7`n z^w@~nl|{)M5`xE3Sg{b5yv{K8j*1kh#beNQ+?#FOivVM>&IRN<(q8F>9SC53Xw$iz z1tTtAnH6#bbo`dKrm+FaeHY&SJh1I~vFsMaM88Uol1;L-=1?GFf8b7>C0mIAMTWl; z*9f+i40SqSN7}`nmQoRjq#WYMY0B#p+4$+FkKLDzRTq-`?U`NI{_C0b29T0{b18ub zY}HPQ*o%(r%ALgP{bYDcsi85q*W4^#8J-_35oyax93E1&-T z%Xf;oYKAz2$jjVF+T72vLrmdF+b>xXAmd;5M2GZU7Q%HIg2p9f%unw2#E;tD`uRdR zeHLjPaeGS4n|EvrQsU@8?e49pM;uEhkhgxAH9;Z&?B#&5hCs+h-L(M{PdG-4Gz+uY z1DLUh9MFuk$7Ei^wCsK&Ff%oSZr$j3d0vVYgk&HFP|ylyL|j>Nq%h{%(YP1Jw*D5z zE~vqqbP|9pK1vq=CCj%eWLUw3UTTAlOYrWWEi0G@&(27~_i-P7a4X0a*l_@vV0zlw z%OuaT?+m+5oL>du)@2I-v-Kgs*lp+>_{Zpt7O_<&1n%aF$bwv>{J0xz_hn&0O}iIt zBlKLboHeelS9g(vOyj*=xb)y1<6bV5SC3w0!aRNTwS~y2N*c1Zivb+V=}Gk5JD-?O zz>Zjm?D+?XPHr3F=3i7az>pb4=ug&RNLVVMv>}uBdU!LZY!Y6e(njt|-`NqiTYSB~ z$!Z2K#RUJr+EZ63*yx>RJ88o$qZF27lxeB$mj`ZTZ$GB3bR-F;rF1E4D*ge- zmIHj8wpX)FB|MJadA_&e~tD^wGQU0UpUe)E>c`xq!M<8pxf zpej{Lz|QN=;Ftct1jw`C1xF8Vd>mW=;h!`@+e0ZI)dS;j;4-2EA|(fqz_V_E0mlb= zwV~pv!rs0s?)~2!3nYzH14#=5q@WREZjT z1&o_VoOwe?VHflOn;2lqL%!33a-0sL^JLY40f7+eKmx~o4Nyf&?1OpD20zjx`Vp>& zA=?4)G?{$-e~vr}AZ)^qTRt%$dd%kv7N^A+ecKv~is+jda4J}@=`63`1gbP>Kp=?a zHQBaYUX$TuTKzoIm-;NdSVq)XV%Ro9FP)H*OUV$9B!{8Jg)mM-ehjFECVJRG+v#2< ziZ4J*1eM(^)vzs3Gh-(PJ^74mYKt{98C<)BjyK zTULxfa!A{Oi8<;QHZ^h-T~}?c>qPh9i8Z_jcnjVgedAkJDSEN6hts<$?D^owGyweZ zUez#1hcr3G#+OivvkeV7vLQawerAFcaLT(yHZ=gD+*lURSQ%ew4Lr6y+|>Ai#H9fM zzW1fv)yo~Q?{+6}_IPULT@Fb`+W)U+*(_rHIG=FuY4|SGSB&9qI4<>_n8D&MQHAe(pSCUAFo4Ww4LfiUhQ#I5p7&&D1NtPg9<#~Z zcTL%4jOmB=FRMEU^Sw#NrORApgGEYE~0qjI3VKLWp>BTF*C z<<4uBv`Y5qwnJhF*}WA}0GR?0n1UB-@B$|FX#JfT4Jd$a zzIDi2Bc{YK{x;(~<`Z~E9+rT>z1csw*t>Xa5L!K#21BM0Qb3t@-g+btz-AWdKl#%@ zC}PSE+n5+8v|QSfi2$yJU-xO|?6+M=#xl^ap>TweLn~3;9H2RIZ9IMH7#f#60{DuF zLkUPT)7KC31C~sWQ%2ti7mH7h=TX^u8(cy(qlGUCjMMHWLIk*rwE(F5 z4od2|2L~`NM*751n!t60Hj1DU0nbP?e$A1o=s9No`+8Ftx1b7KB=9V=qkD4{!2_7t zZdE{c1nKACkd^3m{CgdBxUB`_LNcn1q=5-4&)+nZ9={q&PtZ_G$2D`)LB2{~`H&$S zyExotb|u!Tc=A)ciuM-?!wHQE2{mqQgGZn{?%gX2Sui=A7Ru+Ixc@=|C^GV!8bcJnALc!7s!dX8i1GO7PnO81Gw#el%;a8FXWP5l0Q)7E^MZd!-%4tqMv7HJj)6A zAD3keyg_>s`kVITX^X!i>zWWQ%cQ~wEIE~^=M1{yd6~uXT0LFlCkg@cbEn`2>m@J; zL%<_MD43ao_VK+0KmmSusmvDBwxbz-rbeE7>vLP}=a~A+uztCyJB#`WA2B<_-gb+X zWpg!~0w>f=4MIfu>gZ`LrY{UzAL@nX!!Q}tG8RrX^V$t?3??d z7kSynhjBUmT_dPtN!;w+yvd#8;jwWnT_t-q8+ktkR%Y`2|@Y8-sIEx47Kbg>h~+;Qj>SMD8?-7(TOltR4WX{d?Isey zQ1kOuCJS&sUpW(0b!$u^(?>20WMXCX0rKc*?!6m=bJ8;~`^u_qdA zILu=ru1R#FH4;Woo#>Wg!j#Q~zBZeY&$7k5Tbf@_*L*Ay%Fy`a)PA5<#RsP9v$s3c z@eqPt6p+@gVHke*^IrB!OXF}q$4WTu7c&HJzI>J>j7MTO5DOi`RSt%NQPA>^GjBX4 zIRbo3jbT+VRQ5Y4h#L}MD!kWmx(ldnOot-DG>GMIH#K`5^i6*|!jeJN>MRBWa(g#b znirRWYJ)LnC8OvwHgLv6vFv`eRUeBhuVK$c`+Vp6)LjJsDe$R(aY<;b=Z&yv7X6R# zM|>~x#6MoScR=wHlLWf`A#-L(QvLp?Rok)*Jx)z-`6j7kY*Sgw0Z;?S{4xE@`+F?!C!vV|>s>>R-S9_; zcyC%U(y{^z_V^P;`3@6KO&$@=}voTV@4g8tv4vOjPM|goqd!V_bc!*;vgTPV=(+BMwzFW~>BLG;lxh9=w*0L~eKbcuTL;&0q+@`p*Gj=z zJ_;tPEa&?=)fiK)j6d3~R)<*Boh*%W(~5S_B1otk6#(6rnOmeR6}6W_^3YJfR_Bem zy7=ZB2cdG>2r?d_mqx_OcMEpWfMJ1B&Vk30|44?6i4zJboWV5S41no>qaM(C|Fv%K zB;hNcD4?z0uUn|XkxPpV)#R5+uF`3}7mHj+&>E7J-1OgTzM=Y2p|li?jEdsT3j4v}SU70gS(p;1JqRpw%Tp?+1W|}0^J?doOEt=O{FFAFk!283nx*&h@wL&A8 z$@(|bn-ppfKQE=);0Rsr9y%lU^M76}&?2(A586f%oQK!wU_8gKv4HvX3_?z2^x_Bq zLK;deShTVHcr`p4|{x3~4cm}2K>HIUo{ ztLjg0EoPcb6*Vq$!3TcED%fl6og%6(*^p^%S@M&a%PuyM`Ikz7yvU`70Nqh&$v;w(3H$ei;B}YwP7y6?q4s|O+JjE6!6Ga6-lmDJ`>q| zL4mDFJ6;oH$deg*We6 z2lAFkwZ6&-4GGgyfHrM*p4+idcqMD&dUpw#Pp|Sl0c`xv>I$iP-FHjALO~u2v_ewQ zXsF_;iw4A44xO&k?b6>DY77ux(AK90cdO)z`VN4he;PI z7?_@TkUO#CN1`0P%%2ldvbPnJdod*J(%c&zvZ3qB)kdatHi$se71UG~UR~8+yjee| zX2IV=teeo7zen?~j;bn+Ia`DFk?5T_V&Fj>g1+bTJ&~wa&L5w^$vM6gE~_ryN(fxOf7#n zxO1KiPYL*{h7K!wMV@sa%(GI(Cr6uPTU$w0M*^Mz+->vi$3Dx8K}}MCIqg?*f+-gm zhS)>NTLD4E9yl}^wU97F0YFl|1rX-_)GLqmkOWmaQbXcE@t5;MV>orNu;L%*XUz7U zm+VasTpwgfZsJK0sH&Oaao;&Ow4zK?&7B%zSpmyD^H$zrDo}YPa6ojo3t)T6T3-mnY_QbwskvEgBZ_47$9~V9}05-CYUEm&JGTDrkRV7cr0wSBcf)Wq|?TP!Gf(h zN-PlS@4n86F~~MP4!ksj0CFjjk@UN>wGeRvRFJ!|31E&B6%2bv%SsIA_a+c)M@A8# z><2Upy14vXu00Q6q7ewnh)kW4WkcMPfL-@yKh%1#`?g%F!#G*CU2Kw^G^eJOeg>Dm z6Gw-4tN?}bK+f?G_A&u)Lj8-0RoWc-rx!}EEmu~=cyiZ z&jkh$-_MMZTK>^+&NbmV>K$Glq`AzqN zjTglVzQ~J&gfS2-qu~yqWO>QNb zLCTVP=8e$Yz#Z}{<1rWYta=Nc_4j_+o|5M_S+8?k^7-%-jxd0EuSJl;UrUnzLwVErO_Q43sXV7^&0{p%lKyN4{P$(Nk6^>|YmlA3!C+ zH}V@=$FJ&r*YkrZuPtnSvZ?EoVX;9OQdZaYrd0&4@!IDd$uNn09dbHwuzOvwKaCCz z72=g<=3$n6Da5Ue=sBYneOC8)2UHjK6mc$!3Dat9zr*K)7<0lVQ0&T509-17=TIGH zD+&M;<&}dZR1GAt{|q$O-|S3rchdWSD;_da1tuO`yRn<}5&?SelI{CR;``8(1?KJ7 zA+zNaYyHAOu`fL~1Na@j#xb4Rjt8qUz*rf1DuVy2HDIY9KCRHf&B-s`-?I<4Ih8b%dk_Shfxb4mAYX}KQbU712I zC(X8+b}73V%2CU{yD7bL@O6R)KX*9Og&z8Kh;`pX^e zxV|&c--(`H#^BFKo>Ge*Tum69>s?$NkRd^3uTK(J?(hp1%N)6E32z3I2_20|s|(R9 z17M#Vr~waIUlQ1l`8;w6tj*lw1p`5cZmrBS=(@Q~nUx(H&1BvArsVRmLajzkznoZ% zDxYS&QQ|#C!=z9SkFVB3e$1H~#W7BgT%rK%sB&MEk(T}9_zwzYggOWm2mfA2{Phzk z>Y_tdjslg$2&Cy;-r0V9_BPWEKhktT(wJ$r@_S*Y2wjiLP zP6YoQdu8}ci8lh}y`mYu-J|&fADs&3#8g{f3s=fnUo0CY_kAAWjvLU7FBLmne($@F z`!Z|f2q7z^IjP9ET^)40hK4rrl*)QSxS{#WU~ZCS+DF@?%r<1Zw+FCGjJ@EYFz=Nb0JmdKTbhu;2vM(ply$kPLj#nRjnMFEH zJx6fFF!nWf4lE+bG3qQ>?c@RM3YER#$LY%J7czI*6(uJA(>KDA%$S9TfOZ8zf=QLn zi8>AzuF#KuHw}=n5xze2MMzUVRlWo`cP^gv?e$)tfJELmGDbmSX?WOP` zoT&hHP~@+gk)#y4tV}h~mb(#jwy)us)tbB0(EF#qM!4@`H9hFqB;#q7mAJl= z6+3(l%zH9=6z=jx1saeniLO&;y`qNag>?msS9S&sW4XW@-sZy2&f&kG0Wg>9bmgGz zkZaw=ol|#r2bOoqh2VYh;>f&M+}>;1_5p>KuTUqG)rJcjqZV`1`@GbtCd5Olcn_sX z$-;P&58=&$O{!w0#*T9l!+t(%1}j`8w1WrdcafS0767c^pX|qcd3{Cx4Il`_Nmu*) zmm=k_0j|suU56QcBDJU!tw_sRlHh6-vb&Ss&afNGGOmMBT>z`R!K=BX+wXY~Yd<(L zUy&OPxGk%|dq~7Er&1KS&w^ODzZfv3Vfio>1f?=D7VMRPbr1k@n&17Ct3j&z(*ak5 zRQ12w{Evu@#Xx<&jJguB<{*@{t&U^PMX-&ki8y#XWqe0O3;am zxuv#HbjX`T#2Id|zKp$71r0zc6q*AjNXG0DvZp{yq|Z8QsokF6{*EBn$5O`g3jE2k zLMxEyZbqaM>rKxN8zg-l;3U~+iGG!BRQQol-soA7J-FIkb5)CO$o^XV|MMjS0%N;^ zH!B8v7X0000L>IIEv1n*g!CK$iI-H$>BN9iuA0Q4%Io;gaPuCBmt=zH+ClzzcKjD0 zWymCS2qW@5csnYOtpLcLGF=H#CIPgX!y27g7e9f`7R|~`h#4o}?trB*z-}k!C+|+u z{=y>r?el5+LcF<1g9q=3*I|D@J41EO*)<^$P^1re;>tAj_9!mVFQ41sN18aMoidv} zOO0Fyk#nE-2W3{K(2kjC-cRs|IF_v_s#RI@~53INTS+klw;I zrf@iEPy6)VO4-W?ul^nho>urPG61tnkB_#-Qg{N7i~8i=?c3d>AyYacg_*TbYf}5b zO7V3*f47^s&V;!yW-?vnKji16`T(o31w8|mGH^O~445GNk|TzaSB9U@Omj_={O0u(a;4&g1t8dh(RJhLNvH9=)wCa8CGYZ+VR`@8X>1?FCP4vJ)n(|)GoZjybP}&> zUAl7tK*^&58y5tEO;Q+xEW@ue!PsdUTnwpa6bILvj8aow-zM47@TLG1p4W-4)0n}f z+j$kSy8nxq@$qY1>hP2weh_6n;796}-(k?M2zqof?SEVu((v~mS7zr&f>EH8ca`8N zK#=^@1y)T|jNZdZHQw$b6qr#C&09HZ7RDGiy<5x|Y4lmTp#{f3orr^|VO+TMC_U?)hik*y;~)~Y z5k#_ggy`_O3~T7+J`J@vRJAs=psgkIPR1$W4T;oO3Y_n0)SMS{p?xxbz)@>==`a|SESTokLFfNY$^4ay9(cm@?3&70!_R- z3_0+@S^vooN}iERnArWdKd+7Ei(eXp!YL%`}qJ`3W z$mEUsMGx*cB>F^LT1ys2nnE2x%c}`jVDTUl@HF-QDs&TCxP`EAx61e4YJ4>4G8Zhz zW|>@A&)sTn-kyd}`cJUT7r1X#dP9n5icS0%Wrk*yFYrN|&7L&Qc(&ZS{s5UAzL774 zw2F3p6E||%ZP{Dbf_2bCYCU(ZVWK;DeL6w=oF)#&Q@bI8#ys?VZKl2LdH_j0#9ybN zwoZ|*xcJR(uxjF`Kl@-Vd7|&u>c)z`AK3=S{KieTrYU(BS^3RzCC*8A6q61gF(y5d zvU=2b8*@Ld_F>_vxh<}huqirsi!M&?O?$)JwG-^%IivXMWY34f#V~r&%ht%x%_+wpJXW(rA-TS6qE& zx1Cb6TG`Y{2c)>1RTwE|9h>GS%#1YQusd5mJoJX>6`T0Zb;rpg5uc?$-H+PtWMyxY z9@##1l0^&7$XhQ_k5;Fq<*B{FQt#i#p#`tCyBG)2#GTXq{G5407>#P&_g8QBft9r#21} zQJm-NC{Wf@UaZD(ma?k^~bz0H| zLH+nME9PoOo^s(#p~FAS8HydrmN}P&cfM9HKXf4N6J@;_^FBo8?UAP3*OzdY%VsX; z*r}blY^M;b^d0v#u}#5fdHU80Bb&n3u%da|a(8yu&n>y)Th}I~e!3cSznX72+x=iV zck{`FH;X8T_>^G~BDEew4z|+!bBj80X%s8()5bJ6da7y|loZ_Rs4EpjYOpXduH(+j ztUvARmZ4_iH_!3;Dp+{bQIGIzT#rYawGGy-$jfcWsZX#Qn#8HrYT}+gN(b*C$YXl2 zLJ3&I%(nAEY1Jn$#F_8E54Yyk?L754@i$-6o3^-O^%kn0F=^RVO83a)1G}HlEGDxo z@d{f*V69xfCY(U6u$AY0M83P`TCFCwi<7UQ`8We{hJ0?lIXJAhWp-YCMzQJE_2TBA zTz42%LVsMmPc)spDNwJ^!?8V`GFiE<`yG-F3ffbe@G?K<##!LI`L^4dTY&B71E25P zcsl}cP2W}*qy4z8trcZ@(=JKi(1!GjMfxnUdY-F%nCDX3&i*RajP?ExwQ!m^vVwc* zX|n!8uN9a4)~THaos%f09k;kNakZnHc^PN-?i9R_(cj%^0eJnH$jY@OAcNOoh4XiN zbYt!aV5~WLf($Ph@L=e*qQl>=9@e#-sTjG_J>b25yW%MY5QDF>GjSZqW$=|NeO7e3 zz&;5pu49pCA7luRLw*x7GE7GF_FU3Bq5GKcB{o<2mH{kCJ>L&ff>NpK!|F^pahv z?&s*-{<`_KWJD7e>c&&ApC}njMVlgU63)bFjLR8|C1xaM99N(H&)maIy4ct_Ud5RU z>i6;jNe5mX#}>=^){iX==+bDc7)NeUr3hfwp48rwpDaga58;`Nq&=SShw+6VQWG?B z3tK1N>vxEp#refoY_N9Yjg;azkFV!WJ|Cz^a}BbVk3;64J$Eh{5kRa=FpEV347QQK zZl6qF>&<%qYIge^bPsE{z695SDZhyIP))>B({aD0IhTI5DdB(^M*DB$LZ?yTnK8$6 zd?Y~T&?R4%@nzt{4A1*?oa2#~ZAMO9;)fD~Yc-9}xeIEjoS-*Rx!wqM$KCY^%OL&0 zQ-AzGBk=n8FyHyl>m?&3>-}8~amhI<_+ZyE;?TnmVGO!0_hdbJYIVZD|B4s?{1pN% zs{*;9V?(kxvNj(&&kU4%dLiE_klTQXC4GCJO& z|7{S z0%7jOuNMxob*>j5hNXs;StzKCDC_#;E}uqB1{+lWYcr`vfSt*4!LEChIHWEySJj0U z{&Zm$M7;l8$7NW-*5}@r?zdhF(#^}v*O#>G&6*C$Bg~06AkJhyO|nJAV%nKOk8&}l z+w{iNKfZh%Oj4duj>cXc+T?5-IW#gca>2+@aY4y_;Zb2ln-A`T@ol-}qJ-_YMt6iT zRcVp{B0BPqU-_~soQ7E?7FqnvD9*eLly1(51JizVUb?^gnY@4hnMQFV3Ef76YhS$H zCd^Ez;U{w}ZohwIekB1UL0B6fALgcS+Hl5oK1`6IVjQx=A59({s|9a-7K;>m%7RCR zK6p=)^d=fqkyM`NiXlg4kN@t&Mq-&i2`zmQquxza(~m(cVi&*+Y)-ppmlMUyTFJ_u zfV)ddyrFLB`nGkB0~+Uf>v?K_;)xk7%Q+Hdh;$zO^%g&oYSz{vLAp=wh z;h`Y^4_nNEWX-Qk3F_aN^a+9Mx{O|t)`xX9Bq;QC(z=ZP_|y?`5R7vIU&uG4z@?bE z3Fjx9#dvd3aAOct*toQ1nL_}rcoa9XrnE7bzo`+;0NsX0CysqARl0m3RGPP5UsNc_ zNw5wmCdRWOLfh)^2x@D=FWaR)yoX1Ut=}U)3<9CLjufiXVTwDUn$2J6x&GuU6Z&cS z%FTw&e#?4)_itHgL~-S>>hBf06lI%z`UrdP6Sem~^O@`C?g{3byZap4{uT)Kb%ky% zc3ijt{MA-DN`Sw$Sop8H?rl!%kG{0b@Bsq(f3Zm?;vyv22xEc2%~NP@h+Y_)H~N~ z|Fl}=tupgkE*<>%fYW39#pLaeN({^T;f~w3AGS9a7FkCYw?+o9DG_`U zy(c8rmn$q8f+h}Btv|%qZSky+UrrM43F6au1G3!P8>4!-Wb3@@TJ31C?k6QyD`IKE z2fZ5HVpeaK3RzeGYMrVYm&Wj4KBwRMz4coM;vj=FuTR1oSx*$4<}`eQ|3n_cf%yW0 z9;^-i>qa!3glAc)H6E84EJ{d_-PY(H*o5W*v-M-8TWb^fb=YDp5}z4|G`9A$F1M(3rY4w3I}i=E zLBddzk0p+6Mun3gMNva1wziuePHk`HPL@R7SWO=9wo3YFLCtj*q9sKnsf@i0#G7mtn$an+A zN0g!B%8cOl$=+29U)6b^#LXc=C2p@H*@x@fT=_6Bse%!FP@jY$Wx358Ie{I$8c=JRsT5KOf->_|I$h6K9R)-`kE)2 zFa*B!4uM;xD{ZC+Zo>HjXei$Su`b_8tsusvTbrXb_OmnZ3dR~k3~_vlX>|}Ko^iYw zXe~r08kt#+Y;K-Xlnz{tLtfE{OS_~8o9fEteSj2#>mB8(b21Bboppaq%%KBq7vKL8 z|Au_NZ57Ut<;+m^d^u6?4EoQL{LrLj)TMM`Qg=mA3a#JTT<6DtWTgHp%rn!_)zC$! z+Mo9RsolM$8Zz(y=m9I(lNb&n-&a{7(UKiv&jW-d*VZlAzdonqtFwOefZ@~D@rk^- z?X~I}M|0D6d>WlQkyPbfj&QX$yw<)WE>Jz>K+)v|oJ5nlB&}6JRrpv&Yc%c83@7O2 zp<8I>SWhgS5!V213=$7r*vyNt*WQNBN|-TFUtd$lg~-DvqXJPfEZAE8G>tLVhaV!`JE z7l%`!C%}F-gO1CSed@;JYoXUD*a%UM`7kYe`9>>d(TFc|^Zb~SXui6uYQd){6IV6# zgDL3Vv(O6k)%#mapa2P}{4)IweB@4YHEkNN=A35tv;_u3|6R2<-=35i2f$MOm zic2PtEjVaVTD*-5!EPYti%(Oi?8xb3!~*M9ks!p?<9}SM!x+n86NT{T+W9#KrH$Dx zP7bp99C`>(na4tHzX_p;bCA2Xa;z<8LxQ>FR*kOtrej-)(n@_ryE(70^4DjSy32zz zj7qPE@9&#iAK5!y3hy$s{I9|!)N_MUX<{;;g2qU~?raEAyp5+`BJRHh;LOEiLo?V5 z{2@wpbo|tNK25C9bAx#ht+dI^ZkGEqkTJ#YTlBZEScu z>CyX^k0;i}&0;HRrM4Z&RJvik)z zbk2QNQ|nDNRMDBEmk$>x`^*+M@`1y#iB6=JZems*lOv`T?Oa^BtQLTx`mYzhW9{Iq zd-CD9nxs(+t_v64|1)R1YQvr8FC;b%_|^&Lm#;7F^VOnKSQ!dj&3|5}xG@f*tXx~6 z_nB%_xjXKju-u!VhGRO&D0Eu~hg9_P(7_D}zPyMtY>M^$e~i6(Jk&F-?k8$Ie)$IN2%t^1EN7I-T?W zynpZS|2^`Y=W^fIeO=dmzeX+tPQ64zC;Y!H$|hBoyP@2WFpj^}v*=G>=uUbuWI48d z=?Kvy94R~Y6Nc5iFMIY3Cl%Fr?Y)^gKWy!&>epKcR03&3;JBXWVCc$4&yjEG{8PQ# z$t7Ud;uadf?&It$QX0!QT+g49 zoY(QJ4K8YPeKFHMJVKt61SBLo*6qKu!)JJO_{)%XQtBe(SlbgB4A14Pg8nZxZObyM z=fX|80UG%~5zGnoC^%f4ci~4&`tK!^OCq{5*+|Hxe2v_7z-zm<;7#>z-9kr*x69I$ zxBpLo*c0%yl50`IT$R?_=wUjYiUypxJY8x`FwH^5qXp3hTGIozQZO&jWx&f5-O#Xj zcO_9mbD>trz{F?wp~Vii63fw6#A3u{B(h1fq}D|(@Lg52w^_vt0ZZx?!&S4uBQ zZH_kLObxzUcbV~L_72jxi;d#&{JkZkx3;sqwTW&APiAF#`j!sOm{9XXqX_iv*G_wj^@HgDU37u+>W?Q>B6?KzOc|2t1;jRq}n&H`V*dY1PY zJyR01?tBe*9|LP@ororXH74K04NO(In5Sg9%TTXcK`P7&OeBZOY#K*4v23!3yD$sm z?<8-K=)+Cj{Y1m3qg5*#*~g1vrl~pvy{Rz4Z=sjTd?_fMTO{^{$*@CuH>d7!MBeKv z_cFPKmaCLM|B8%O#o+~U!}S0GPv2s&03QhOuv{Bu~u%ADj{d!7CKvd!R5ud<7=d@E;jqdZ&FLC=4{$2wAsvb~0ws=qwi`Z`J_xmYWlTJleMDB4JF;LW ziQOlqUz%nn@Gn%&WtNheJYU8>it1Uobc<9|*>d2@R_<7b@wou>1|xw^1zDOIF>`g- zg42&ib1eWa)sNeNH#kuJk99pdOVF{g@Ev&y$^d(XtN+m1w)@6O1mIe0cIL>mNj>Lk zSla1k#qtXE0m~ifFD$@<`^FA9K|S~hkMRCDbX>Mvt6=tND1_=@tsROg5%D4+K(MW{ zGRY?po^oQ&!f-)XgIOfZvZenl5^$lQ7BGv1zkg2E5?7hYv%EPC(=dk3ZQEH5JQkn0 zj@lM0&cr)su=tSM4sy@UJ$b~4uv1>@`3uE}j!YF*s{2LPL)eWoOPb$K*k zX)7*ZP?=m3f2Nwg@;7(xPF&Cezgs%Nddka2lGYn`-Cs3I@>D#-TniZer$EH$9yfuk z8pDajZ?EE@+Q;rq+&jleXuG}3@w(SVrQ~o2^8xsB^_)sFX5?5zNig$PR^2*fbsK?+ zxG!d6HrAe6w;6H^M~IKt4Bj}|N~h>8S?3aj3Hm=I*RUeEilAeDI^kM&Qtabhs- zv=5sjkA1(OWptLAZ8LiUxgQt_h*u~r9}zGjp09@N-lB-tntnS>b**UYzuT7DG?`*@ zE|xJ=ar?z=?J>%v-RR;~*N(hEX^Sn_1ph$NYV2C)N-THqrw_9}q$1PtKx+ zgXPwYR7=r>`T;5q4*Y2ZbiGigB1Vt6%d zX#0Gi*je&(8>Zx0ie7YRok7v8f1Ec$0(Ow~2sl}tZh zEuFOn?v=i#_7iEqTj;pox8RSYX`a5z$cNafObL+1*G;@VqFzjxjid)8$$!1Id`BKO%0=Tvai!razAw-o>BF@m8)ZZ zaZKl4;ht-g9D8$Zbpdr;~ zw5t9@tmNJ^X9ztZ8)|c-89D_KQO38R2;fFlS_odg7oBp$zDTzn)o!*=Qe?CWLCMS; zIX(C98`)#;>Tk#VnO_Sq#XTBTbY5~rE~&pIaH&NWFW2m0MAVof=Q#xWeas6rIH5#d zdG)%Cp?0fluGaNDuCkL{pVfG=O$H;5l6r=f3MLFj<$cR8FzKHDwhv?^ccGWkmvg5UZ*N&s!Pg`0O~7>A6~A=$?!feI zx}|L)B@dN*cRuIiyo#$vM|-=Z^~~Z)*Q-Tj@}k<&5i!5>y#)f(cnV`aYnIsV;p4lj zL#&}lrPa|YHI=@}|46_Ltr_##MPiiK>o>lc-dC~)SGMwCTIB1S*;5uAdMa43_$~XC zg^dJ+k695i*4))6Y(6iVWk|3gdo}?MMBdK8zu!)9?2B(VC`(-4RctwVxhpRiIt$~L zy)Mm`2#8g?i6+lRk&~I}6RA#&6VRbEmu5BgT-TMh2-v>RpJZJxIoG$faKC}cr$yy! z*Ary#F<$Xbd$2rmSiRfvHoF-pj!6YhY zixKg{@6APX71}KOCMN4~1MC|Q?H{+vpQ+}>F?#69UW-G4d!+FB{3WS3(Jw;UujwqC z*F4<(x$kO4+-K=BCrm=^&T0I5LCGBL<+=W$Bd)IG@$mef>0bH^GM25umH%^Ct!8Ox z3x)h4UE2sgd+#7`6xkm$GMq;vE(c7ENw>WZl+QM_Vbv0lkLoMbT8&6RN3D&#cUm^Q z)^Gtbd#9^9+-sWNQ~zG4s({SY^jSMbf>j08kCm7v$xygVSk zZ_8~9oGgAzH!I<5<#4SNd@1Pon;a7Rp$+Ju8GH<&EPHcL9+l;sklz{O>^wDzD9fBX z+wl*CjQGyqVdzkTgS-F8t5oMAPK-NhV-6w?)T0D9<@yf}NpH*7N#mAtTaUPiznx02 z)h`$MmgRAMJ%^VnBcy7?^*4>q`3)b^GQVYyT=Z+#t2I5}BSu(Tt((1OIM2)C-or^@ zg|q7p*&Pay4K&mu-X(EoLCUU}U!#&{@K&#AA%mO0P2;D_Bs7R9|>y=lQd z3&gLHf?M(LnoRb7JFaMOU_pL+PQBt?FAV#ZQFBB1@pnrq?`p@13vyWn-MkpS!lym5 z?Mz^MW#?C&s_4-=sNkozXgCK5Eg2BD5dH05WI7B58%19L5CWB0hFCSOVRLMcZt%b& z&^Bg&K+PMxl@DL8X|3#ea0PU))x==j9FR82_VbKdh4Y&yfaCVZo+RsOI2_eL@D z$2J5`s~ZXouN!bWNU*`D_q$w!0?q0%L#*?et@g~F`iS1VOg*;hZra3|0B)`o>mx|g zNw|HAa42*J&(CmQG{IX;DV^8nkJgM$N#B%h&=Tu2%gHC0$@u{RCq@JvQyZo5q=*J- z=+rNvRJk1`l+EllrC2P^UK62IZdN?gj4Px{=2itzB?t1qg?jL6?>M7<#!;iAn};aV zCeU3uI3<&>DMF{YIQm%BPM1Cn^NO0aKO#lz?M1?covAQLI)d4Sq#C_&k(9#;=*`=^ z{egg8)qwxC1*hqoL_swf+7P;W{*5yD@vg}c0P1Q1i)C2qW>A?v9TX51^zT)=dgUc= zM6e(wRT48qj+L=80dA&A^Uh|x8_FKEhbFbX&ja4<&m})8B$Dhm1&9l8^ycag0ORbi zEH`mNeWomO>6&~=t=55sVN}svJ#Ip-M$b0t)AB@2ljqU}Yr$BPEH{krn~)j2-?^6$ zN<;kTJU*oV8A$tVvviNoZ79WYTUGsRQJF-5M0?B2;%u@e(Y09N^XUbHdh-BACibTC z+B&=UH6;=YIa=-(%^MQLJ}~1aY={pnxWbOqKi%Ql*m46XT}St#sKZ$NI9^^o5*yw{ z3${B(viFw_M{%kX{?7%usy)R1} z5nfL>V#|k{2p|o~box{8s0^EzosHWpG^EL3&h;j{&u~>$t@21)6XJe_eKcDGtyp5haNCy6YU(^>58RMDk zyHA4=jsXRWG2tDs6f(mu;1LGE1%oDJNqy{!+z6-7RAQMfD^Fyw34B15oJ}h2pUh45^J5I&=)J!Ys$ zL|h>?JNZDxq7f4NPj!yP zdfZ;}JD%PUVfUr~(s%SSIh7iljX>nAwGhVo?ZsU>!jLwR!b-nT3Ie~+#(R9gMvSd2^pL9@3raC zSpDJ~y)T%4*N?c;8}(oGcC4A5eRm{{%WSp&{P}KMq>#jQ7?L|om``15&s#bJPL(kg z1m`o!{BJ*x{xL)8kSB}px!Sl+K}*hofQ@Q679*mVus+BK3eBKuZGlGt+Ut%pry*m! zA>w+nRP?`xOhh@-8g1UnH6z{w>GFv&p+_UKTutrBHd-%r=d80f5nwrAJz1;ygb@)T zuAr#hT0?{8o}rxBjSXg#LLF&*9-p=#cE^%$L6vJUK|Pn7Oxdb41hAORWc`8=+V* zG&|-HerYAv9zQuwJG=jaGH*+2g^P3g2OUZ3H=+I$i?IQVje$l)uFW(lJ&zw)K`1oy z71XsQ_Eh3061)D1jkMrag|h?};T)$ttidM5x_!k0;C}coYjQ9Y@ZHKy_4t$2AGza6 z978M`(8Sb1;tTO~+LP`wRWy^Q&{3?Ie&bk2U;tTm?Qcfq5Wxohn-`&HQ^bt}>1RF)`2|p{aU4W~aUGemZ5SHi&HNxP}Hj9Z0`vy?{>lZ@s1#Z>mqc z@Sj~06*3ZF_%Iejxz~NRNpbBzBJO(=4<2$cJy*m5S$Ei=hYXPsFAR|o*p z8BU-&+5*tWPJ$S=-H2!;zjEdX!mp}N-Wz^Ho*Qe^2waGC8;4rKg)79qpI&)$MD2=k zvP_K;>>i_uY z*ai|iXL2n+KG|M17NB0l68LRQU!4hVJ3zCg){S31A@pBv%8+CJb{gxr#K3Yv0iRY! ztUclym~`C@8XQg>YOz9mculi-FRu5|k9(CxMWMAG1q} zB)DDqFJ}d7sE@aYNjm)oHrN(rUH3Z(QtLCpadE$=f4_#$ zCOt%k_wW1;{(f8H10W-B;ubjX9u1<9`SMfqmTiz@baT@5vEh^!2Xf9DBO+TONM&kD zgIE8$pL9K>W_Hwzd>41t+%tv#TG6^v^PKNx87tRUbIwDPo+ZzO6z*2noiFkC*`a|^ z*pSNgOQ<~oDq8L(8YQ#FSilCg^Z#DBExrnMx~iUKRr9DTy}#x=UBEP9{K%n)Z=q+G z7b>3|CD;U#&`gz?^Kl$H+A=B2K@wK{U1^pXE)7u@t7aLTpkmVF&3)~3oNZG*63{1S`=m<_(ri&jW!7`? z{S;()z*~TD8NVzJX!1CX@wR0R(~qJp9*cj8{Xt^9%#fQT^)qM*JCY!?i2}EK&3yvc@t&#NxalyJ2&QN$ zH#}!|!6x8GxIv6LzuVjk{yRDr^{`aZfof}2yG};MGjuMI7|Fy>)|l^}5Dz1Px+jeR zpp>%4SIO-!#@U3hv*Ro6*)>-dr+g}o{8W+I$rIF5T-B6w>~!S2wk4`XF_eMWnxi!) zsNo{&oI-+Ryk0y*Q2CeDgE7G|?bx_Rzd@{It{$jPqBy?>1bu|nqv~h(&uyOBnG4w-H4Ngd^Fqf zJ)1s|(40Rs<6$8YK1M}F)q<*Ei;Mvgsoe)!YDyAFGEzH4Nof6gE-!b!V9fhG4=f*$%GI(9AU?_Bk7<)uj?iXmt5@gkJr83w;vX zpBYA;^;3NC>|F`l$Nc}us9@OpJC~YY5<`1c`LpHFqg4zAQZ$w|PM#zn3XL^pB}2F#_$Z}V_Q3sX{d7`2}YAtl!54@)jj@Vy7ixapE?=f`TMT`WZx^~lo%TA|2 zWD62`Ew_GRPA>D=yWOVFtE!bXV^=)(Zy#O44^99qA|sDsx368M1RT=(Fvj)oi+reTU3C>yzH(f> z*nKrj)9F887hIKMeL@c!Y?*tXRMTmvu~c9Bf|U`GQWj5*{>69$#hE<#qlLGLeQm`( z3gX82DW#v1SJSaJ{2hZ4%G~&48{@gc%q*dqp~lf0h$8xPS#A{Zx(GbByIVmzEr5L2 z`I3GN_@o;U0*2JFR1u9CM~x?1q><;-OKoXOTMPeKmm;jY#*EapKy=V}f+tx%8+fin zO$1pL{9KVI-xeYTi*4vrnwRVE@cRCyF)o;jNtMXs8&dE^iKH6TjadOVvp2Onwdgz- za)zQt9&V21@#=~4+d|^lX0D#$uW6}j=6m!X=v-cnWoBc~@@@(%h`CA;`_uZy^1;y0 zL~)5|jVF6M#WOICPdfb{QYphPG^WFb)=FKAcxE>cJp6cXi-X41&FNMJ3p!=pOl8l1 z-$#zbZFs&{F@Jfvt|qcvx3UVz%H7u1f_s9Yl$2_Z{6PmX_i`}6 zFGvR6EIl+cZtQcFmYL3-!Q?G?zb4199jM|cLjmE|64%A=VibZKesQe*0z0?y3c&L& zYh;<(I&--L0iB`bsaETQgvHl+iwB|V8d0&EYoR{hP74lJTaL9ahE9f;b_}m8UniZ5 z<-@?loQI3J69o16pSJ7mH>IE;WNs2Z5TSsj!nYu(Q2Hpm?BU?6cDfC5zSf&Q8oIIj zgOGwTB8U$0W^VaM;RBbyDQ&OsL5Lp`lWw3OqTHHN(STTl-vK;_7kNO#ZJFaj=>gK$AyYU|(5g{8VKe|GhRiZ@l4#mAXm>0hH3>hvy!jhva86wM>0 zx7VVnKCj%@N99bS?)NtvgPho^I=6aaP@nPvo`^_~6-S-h{5&D+5tLu$6J!5>E6Pva z`OAt%p%w>*H0}e163TsfG1=XU7M#8}`ZKT8wTIVFPvtNT@4~s8fMR^i{sTVlmWw__ z$S~Ix+LOl^4@2dfx22+ow&3~D#$ciS8Pi>u-srz$`fM^A#%pL@=mMWxAVJ#;kcRocOU**f`b6_Vv}BM|a(u%qd>4TYJxKT;!dCE&wNTd|0FUFihDt(R3$8>1Ta z=W}C$Ap7V@uYdp9?~PC2Ns6C;)rhb;eIH@C^VzOIi`w*Xzks|QkL(MX>7_u^({iP4 zm}t_OIY!os@FT%Lb z#Q~oWJt__Fk04yj&{n+G-7a-6N{n5ghu6`MJMbz+y9`1fylIpPds;6}UGxtXBLQ~zYHP)D z8EG>?Fynu}aMi$;z5bwe_Ad-ML7oof4>7x}o|9^&NHE-Y1- zQ5zsGOYLgg*y-Zl!d+xZQD4$fzB=>S1(#Cox^gUF85Qioq_^DtD6DZ9`Mka(#G4Eq zvt}&RURjrbV(TTgx(8!~ZiK7>ABFh$&Y{_NC)1{tcV>D6EfOf!=@_(-u<&e%)SnbM zmo)XsFO6M`x>Ts%+n769gxnaQONPzDEnnCzHdDGdy!`r#+HOB=?Ovb^zp*Si5?emn zK$MiQ#?dh%Q_s|@zIKamEgkvvdD)XXSuS+Fo-0~paYOo2Testf>6V%H#(HNVu|J@CZ-P&5`06!~N}amoFYZu*V? zd}xTvUDN+f?$3wDMRQ*mDLti#XGve!e8$l%abjZ?RD$1*V#7tMUiA62bjQ1mlUUa? znu{=Rre~V>vHD-hm(;#4B-+^;sCeYkn;Grxm5&Cg0|K)9$r3f?l7xLWE)~ih^!~wb zPE?z@6V$>iuZd;f4|DGLM@kOb_d9sFCaMa~_$pkokG-GF5UcR2p+8a56tdFH@DH%Egi4|pk>>uA1A;3% z_SrX>gvV8;{VY9qEZ85z(%$Srv%UFttwWFhi2`yV0*$PQjAVH~DzBq(M}OFr3ajTm zY2KJxo%Q33%g>kHkwGBAZUx1UDQt!W-BeMGM_4>`2+SvNC0KyWa| z8f-j&3oR%t_R7a6c*b?}69XceMp^X-Z^{Fm zO5KVdcZT zm>WH`1eygLG@-?Goqvy?U@9A?B^i)!}o5O+&5%%?be)vVgjAVux3Bqp;!T zd=0!dma^2HJp953pt}Wnu>G$G8f0#+j^ZwEZ2TkY;ofbF1(~VP*1bk=;;VA$vJwJ~ zv%e|#R05dz)U#NvH>ARfq$%Tg^uB$XR&v=<{mWPXxQS+W4^rXl-u6Cytn{u`Pm=4< zSAYJE3FyU*Oa9T45)%vZz7|ls?dAlDp7>tJ$|l3(ulM(%-x?KXw~fmz_qEyVTrz)(O`;*UghP?X=gc~ugp zBu?_3mUf;ZvyTkO{42s3!P*bh&hOy6nHsbpnG?m((K?gJNKU*QnYf1&If?psv>a(` zpru1jm85Ws+yloIk6R>8FGUU|*RM}_)6xSyyX(ZKsA}`tOYQV^?b?ec60@?94aJN( z)|CkKH?7=HTppO737=lqayJewJk^`Ey(;j)Tt5jkij9WQxB4dfhJbjEt)Q@WS|9LP zBOz04=1&QQK67(*4vCdtQrec>GI!Mkkj(yMckf!`>gZRZ+~LBA=ypeNHp4vpPyRn+C_1^ zsi_FC`%W-&N}V?2yOu`zrP_H1IP;xpO<#2>p?wcQ`D;C$^IQ=hn)$p8ynsy30B*D>@yrF?N;|Q%cDtL1ju z`ldmxMvpYTpOdvxCO~q$k14B4BRyq^XU<@P^_OgW!Qa_!#cLm7wj%VCpVcYWG3_&z z&hh|M5GC;CRiL^I1%-!+IG#wEmkP3Y zQ$O2O=AoQSE*vyJ1!6_l#8ibhVkCJ!?sA)psg9cY=?I%(E`P^+XE5TOD=s(DDPAny z_ovckDz=gnn9cLcKCqmyl}@Kr2Q!d3l=~}4_A{0CZ6X@$4!r>`4vN!#0s3h%iT9mS z8C5pEE6Ypxh3OG--{1#`d=1Vz8>o;_?rhGk$5VCgvcL-Y=cdPdYuYCIsXG$TuSRD1 zaCnyK(uhc5J-+|8Ri*m~`Q|LG>7RB*hbPM7`7Rk3`)w4C z5m#uAG9*g0plMIqFL%B4gX?h3JY{ACe{-bbvs0OT?HOZ%9g;a2K|K-JaLH3bx;H_Z z)2Wn`!OIVr3mlYf+`hqSgO2;PGO!=4M1g0s)fRe&H(wXkE*}KPbr5uS10f0pHJ6#0 zq{tY&X%p;PG2xhL*B?vH{>PF}yRpz6!5=R#r1QmHBsu{!rXW}Q;v=cwbU3moGBU1|t&hn~T#ZD1!uXE@Oc}N_7v?ieUA{ksMIny67Y>_0o@N;e|@L zV*fpx0qJwwC;2A@#>3RNeqVmEV`G{rp=U)72lNRJQHS$QjEGugTl#R20idz*WvSz) zUw3wOnV_IavN_Oc&{=FdTVgQuF&N2bKzp{n81we5ZCGMbIa3qemJ3CDUo37?*pV4e zsW*N*xcvR z-ZijjaV$5NRM`&B(DGAOKC(!AgUlnbEhN;lQAS2N!GOs`q)kb(e@lldb3oAEzhI&AVNuF9Y0&sVl` zcQf95nB5s2I>j}Y%}-*V?T>DL#Iw3`f&8g2HFFXYV;%lV1CbI7)!*P3yzG(9^4`rE z`*66;*H4kcdU88{k+UJ<H?n<+loA*gY{HmiWx13Sczg2A5C!R9aKW>BP3Taho&xU^srRyj|j;q!Ti@#LX zd;PX7w_VwGDEmUAtQDi5!FFHXH^MvmP;MryoT6X36(LkLGjQI|k7SlWQ+Bx8k7Sy% z00Tf+M~z4AA@tjFzF-<1#Hm;I5m+z@PO19&z+YH~L-2xrA=t@GrU^Z_h*8zUKGs$S z<00knR(=z;!wdA4faw~{vRj0afODuzeb-xet&Zmvw^B?0nmRi-?O#&#mj5hu*;3@h z@P`o@=LKhQ7`u{RJQGq(zZ>(``EEa7Wc%Gb*ZQE2lOF{JID`CxLlCn>t=TFU;?o=E zaTGY~n+!3LIq={9ZZi=yeY)V))KE_#`@ZiSu|zF50@4PSSvswQ7Dx+X9~JO9IKIHG z;h6fKA{X?rQik`?S^6DthhDwqmXi5t3dvGun3}~6CL1-J!`68tm~tD1X4%;f6ROe# zCAQ#MN>b-85i9bEjH*qB#B&{v_lW(SuT)BLj!C7ZR45#MFV9ImD)S@{lZ{yqy+KohzWhkPw+ z5xrl92nE)F$z56Q@rkJB+ak#2XY&3>nB6IMjv^VRRk-Zr;wthK>rzwJ+|DZvGO?OF zx2T&FZ2S+!PUGcDxV^ZdM~{zZR-szVM@#P(=Dlr_;OY0$dskUfVkjp(=6dm>MtQ}H z+b+UzI_0^IJA<=*R?fBs7A#G-s>gnMTq^|_w&TO6+v&L){)>_`xqQ(K-R@o z@v4CFd!IW8>$M8|XD|T4tJ58dF4LdC7#U`8K{!&=a?cMs|38ij7-2uN(J>I~lGkBc zoKl(9->*&Y8h@~aRoCOlDZIR$PlMAPUIr~8UAnbgTnXNk!4~rJw|j#&v|tyiEO!m3 z7x`Uj=2iQmQ{yv{UjyPBhq8HG$++#~T@q`(h0n?HOgc1aZJnVK?m1yTwKseSSP8J? zs;`T!jpBPFP7@-hPmJtqwR#nM@14*e*APPrJhUcFQm8#hblX=Ck@=H?q+#?^B-98@ z+Y+&l9Vvb;2mT0NP;pky)_FBlhR$lKm=Ssc6Lan|l+>D=CU+u9?VrCcNS2#RUrk`~ z^=p_kd+Wt`HE-KY<;)8|rEvQqgHz@(s>a*>DB;>zPz29Hz=U?d%(b)GA1gr_J{@~| z26ecjBC}-b%8jMBn|9e9Er~WJ7L4S)I6_EXV|!aB5!!94K>TALLOinWh~FAIILhF& zGd!~4_83?hh-AeYz zhZs6{le+#tf6EYW>WN@oisUIu5J%`_Uck3M>@(Tz*iqh{Z&{ltz;s2wNMNtMYCdbg zsWGVixC!^MOa1=L2|}C2Leztpkdns2mD_frZ|(CnN$8A+FCO`1UV?`O)0!+Y456tg zv=3xVA1d(mXIwz-&IBXSLw||{Le>aCT7ijNNEc;H_TIAVdQ$_kznQFdA^b>*D6a3n z!wSsF{KLNX^em4g^eIb^6Vu>!@Xdu-y|iAz#P&bqLcoL^7gkL0UW`0^caDc>d-389 zl!QE9=^c~^_ObE8BEvS347sPcw9zq~-^bs-F7r&s<1p5FbkAk4*!Lq6IpQVtPi3~= zq1wI?4NDzJF~Mc{f+D#683qis{BH zFMoI}JH2P|(4EgqCeEkM2XZdRIr&*)u|U;QC}MV%Y+wV`tt=Z=v2CC0p| zXATO~%-9zb|MO^ggn7k=d(Ce8FHC<5Me#)|QR%$1pHr9?xt*wW9L~PB&Qdjv=lr5)SagO}If>ddxQW=>ARntiY8{S^P zPX6Kw-2btskE>WX$QdE!DzjV9u`a*WR=#La<|aiyzo;l6^)d%e8D7pL@40hH08s4) zWvSm3kp8f64 zU`k2_MF$UEv2mMuy(90CAwPS)lG96NBeyjLYQSxRw@;eG%Uo(K2h8(5Q6IT43eF~i{jcI>_Jhr0| z=V`2qUhGpy`FBbduXTA0Rk$)2yOO;0HCsFNM&KgNWD;6ld7$=$kvyQJ4(?u?Ai>hO z(m|G0z4lNNN-mg^RuL+l31dnMcI?2*UxzaNIteZF-DpzxVJfIk=?>EUV-ULpRD)p-2gpF2NpmpSJp<3$%3&) z>N!-|N`Ej|B2;#6itI;5!N(}vcy@2 zdln1ELk(H(o-siXxI9jf*u5Nd&gj)gR;jeAoM}Ek8qf#rZ+V(=seGU~>4_lS$%J^B zYlDg@s48fd#;<9t1f{IUT_ORRDIkxt)To1}DM*BDA$FM(Exjq-mGddC%zvLiS&-we zp%ujn>q@`T&XnJZ11~G{s2yO|=c_OG!qP=Ix-p@6gI{)BW6TL-;{GwRR{3v@l=om% z^3QYB4(NY0JZjT6X651P*&Cx;6g88|o46;z!I-!>l~$$-4SB0$Xe=1J`87#wvs>+h zk;YNoH{8swQ5~(2!STPhE)E^O4$}bR5Up+i6J6>J4Zeqf_&gqZBiw0YM0-Lt@$K{l z8>=7GQ8g2)HHC^OpZ#Y1=Y?&s@4r%5wHq zkrZ>??<$@XUcTr06qos-zQ(DA+lv*Nqpb}X)#k1Y|Fu$^09=C2;L6DxYl&1zUwL71 zA(W!UtJ3&EK|5s#G~2P_5)y%xwPYvdzO)a;TcB0zASb0 zg+XD*rIlANCsSL{lHE@ya4VCzwrs5h*QvR0hQ#HsvFYBM%YFMT@^rM5y8KP@GRU!# zErTMns42VD=8c9SyfduptDN%zw*iO&aIK#doe4K>;F>R{2$%K7hGLg%DS04y~j zh1SJHfL|f{(YSUr7%Uq#XXu4vel&*{8TD$vQqlEn*f@@ta`nLWBMvkD9kwL zPSdK$+xcdGE?|jcpfp5fuEeULq@jF7?_$Ku>$Zr5bpq;eJEhkbhWH|Xhc=S|PB!9L zYk_@C0vg$b&!J&lJ#z>W_%C;ZE=voZTZRjf80rs0;+o*`i1Ob#rgL0#re$UCLR0K= z<05@!v2i7(Yw1QKqkeg4Lm*xlZQV6rR50@8S%SUk%Z@DU1}|~G=0*^myv39%HrP4) zXFXpc)?X{^nCnmZJ%wR7mQ7w76o|RfnO&;?ePeQ~PVXt(z#h5_r^cdKVWPeC_1d5k z9N|cx*X5fxx$MVPE-hFn-V{=(%SpGkTY} zflN1B*#Rc{n9KJLg0aI3ICw>TTnkJE5EKoMbvd`vLSMGjT3T89pzRR&E!QzTq<@0! zO|UD!iZw`2n9;mt`Q|A3XV9HP`$OgQlC znf$A{tLPxJqjihI_?Vwk;8&+gzSJq0kql&fBHA4DA4I{4k4b5}w>wTvPG@%=!@+(dP}p2@WD9rtG#N9iDFp~YY2et9#*cUIuWWMPId?{I1H6*2qy ztH2>)93dMuqgGK(4v+9!9C?wTDDT$7l_pf%!<=A+)W%}J`z;MwV~CHC9-ti*3H6lo!L4@7Gd->*0)Y%l6^ zmu|jo=*9x{y&{d(RFGhG_LQkSj79$P@e#N&C&D~l!%Qyslfd@vC)r~q-l?P!m9~N3 z`DR#0u>VByf87@FkZL}M=l*;$YJ2o2>w20d@aFc=D3tPS!eUx`Wdbub=iMu3rTKbD z+(DK)u7BIiY_Io5$KD00P7Um2rnY>y+faT(+79Lvq^<#vzhUi2S@GuCzj=to%VAc03Fp-P7s2?LD{2A-|)j_ z!7jR~3TMqh7agWI?wW07%A)L<<_ADM1Y!zm2rRe+YAnGY@adeS>SS^O^hg|WNqJkj zcx&aR<%^Xi`>EUSNm&Ng9A9}ZE=or%6-F#Mf7#Gab|c&?6$lV`x^js(C*0+cIp)>c zg%&TICMqF*@optIes%j9HmZxBZjq#)Bm^Al&f*V z_cqJ52k1O<$v$kbkXF+9A<+2&PWyEZ?m5Q*r)epJMJ;`)!h?-d7(9ojamZk0zkmeU za2j7|(y7BACH>CtPG5Z+D$KV5tt+(PeUj5#X=ZthDob%DWHydEjbPVy$Cs@ON|Vw{ z->l3=c}J`$-FLMuxNB!y%Xe{^+G=Av)6Kd9YBaXJF5xb$tqsET7o?}X-S{f~<3P+9 zcj39kB4k#JbsW`ewDa}~+ve`4SXzND%;k+k{~zW1t3NCW(R)Xao?l?SUy`cQg%Zv( zIQv?D228xW0Vl(pPMv*XZ>>?LdQvm_44q(5tBxG6VpsgS7l{D(Sfw|bfvg|&G;Cm^ zK2Tlv6*Wj?2Hxnd83wxaK=sY zF!VIQAdPAn!@W+Dj|$yhS!*Q2ClIwsFAG=!Fy985kcK!Sw_5CWL$$+r$ICRAMgslhJD%uWG)fHgR?i}548Cl)KzD7z3Y!se8oXYszgrHOTrmPR`+WB;b@Lfc(GC3t zPR|?_um)eUx3C8EiG;4@%|Ke+ta=x#eMD0O^#?vz02+Z|y1~-Fft6KF5h-ADbO|ih zZU)Si>=>Vu{%uP7v|umjJTMPIf0g>llE@ZBnze>)5d3Z9Z*FQGp$fUM+?IJ8aL zHK|YKgUsT!wiTxDSQp z?wKmS2u(&QZ~6~N?n1^J>uDe(akLeqo@{k+DrV%fZ9k!BD;T`AR_Y7)z+G+b?9{9& zn97lJkPxI#8rr^3*#jo-he0#5(?n77QxJQjvPC8;-#LOC@7G;$j$k!mPZU4!j|D(k zya0tA{x%iLx|?tfBRow{5^QqTDQ%f(5fIOtoBQCD18V)Y6FPqhiAW`KH>d^*B;rjR zK_521&tRFtHg5?E;o%gGR7ga_m*pL^vEi2Jn1?~!|HlHHvTEx->rYICu+Mt4k3z<; zU0PEXTfP}TyErWUGt)EKdVxqRx0SkjeUyPq&U%F?Cm5sftb>%g|`kM4mhsHlo z!;3hnfUlA_dTInSj0?i5c~^*7ZbhA`g95^_t*!~M5DtFpWCKpko@!>o`RS1Oy~lpP z_y|1$wR~V=!kxnk6to|U>5zvZb6U9a$R7zd3*=_qVs;!J`d9Ccbd=&&*0y~_`rDOM zDx(sWFZyrgo|e64F1d`Bzns>j#i+8#n;JlPuC2bOZwND1Zh@Y9%yiGOxxSGr^O;+zzgZC z^Z|O!p6oPthMcfJ_K!6Mnh*9q=}USGoXI<38tc&;#k7o9+jrpAh@$(WB&Pr5fS|A^ zsIfGp9o5M!se^d^O|U%Dpx}3af)C|?NG!h#<3do`JiOVj6I{>^0R-GMWx%fG-^-!y zIR+C2nGLC%RJJgDViJLW4yxYFwy;6><2vMyoK#R_frVhfSh?OsU9Q;F``(%wa*`Ch z*W*(7;c5rBrk}J)R&^(8>^ko2Z9`#1s{)`1vLW=*TvwUgt)nU5Et)QusMTFH4}8RL z(6q!tl;tkr^poU4-s<3{SN%{#UyrdIAG>cXz?bhBkJcaDdM?adK&|npw$#vo0&*z3 zX{_?WPk80Z?#RRVi&oCpRbIio_KJ7R^!lfAGp0GcjwhdO8l4E!0%RgAFA1RRNy0;g zZGcD)q;a89Us;zAAY0OmaN;vEO3+b(3~fmFwY&DuixTQ!EWmD`^fVF85{6n{SHx+$ z#!Nyj&n-YdBuf#vc?PfWIzY&n50RGieTkOOR^LUAGNWL{$N; z^y~hl@Osyw<_}?cj^@K@F_lT?CHC1g+nQmWX+H1Gj&N*I3qo!f@G#hjYYn`FJ`Wg) z7Q2zjW=m|2&C?|W)j?;(H6=CN&0FZi-4rBYPoUnqO3PJM!I)xRejEK9krEPmO6aL9 zC)6@XhiB|J6!kq-+}v6W37bI+-Xf%$27dASp7Klf8~s!oLZOXfh^-?%qF_-x269>1 zLq;n!0Xwh6ix<{xb$v@r)7lE7E|0_+#GAyQ%JS2I!5GN7iB8yT$2}XdPV6-f}!MT(7xxka|;<3|KkRj`KetWJhk}k*FSCm4OcDvI#@(SGH_bR9g*SI%*|FQ~N_!W2 z$`HOLsf4Pt*qs$o=WY%{^d-eNKjUXy2&6G+Wmx~qfyBzpxBonls(F+fS=EwYvoU`} zACGi6e@4z63d^$l>YHvd+Xt2mRI3;7X6_-$cu>HWrTd0$fHMI-ZG6xoS?2T^+e=_R zonlTryJ|YCKXo@!(c7sgdJ{fK%36faRwyrO=&6(+*QFyQnJlXM$3uvy|Q zqu&L6C%sY1OcF_@#}Mci-c5o0{*BN@>wv$nOR^t#mAVF1wK<_jbYcti3uMB6oHH#8 zHYAu!T^vtEvve2{qW>KLBKkJezVC}33tQ?y0zh8?M>9ES;l&LYNXj)eTOm|rJC{U_ zX+k(6Gk6y@fsSGbVGm;uRxV_0gCb=x z>a(hZu)F;3pxSqRpE0K?Dj%@nH^5~$AJI-)4TlJLlcjE=i!44x*sd~6@HbpffGfBL z$-%j}&ozLY7$1|Fxakh_FCQh0>(?*i43SiREnJp+J$9uqva7ZiQ^0aK4$Y!(y_#RHzr*pA=Y?O`Y6FkXf-im88uVkZSvri&s@Xc_5EwG*x&>UY9LV)ZyhX` z{U>W=rPK0u+k0jowS>L=SPRSUum0Nje+3miNJoG5gXH%o7md|fq5wjP(8Z|I7-lT7 z+ZzFYwb_U`m{~ja327L;$vGE7rwVn|+LCHG!vDX@t~?&<_50Jrlyzi{G02jADani> z%Z#N^Q$iVGltdzg$xbpTTasJE9XBb$psd+7Nnx^N4Ozw)WipZ7_Zj!TZufV;fBpRX zc|Ok^=Q+>kInVo?^FH#{Cmv@O8>V&7lFj8*Au6c5?pZ>%Gv|1;ytCw>jK3Vl!zqfa z=su_w0PS)9RyCC>fB~ptLm+Q>6;QR2pghz-u>TNFaz}tb?F+y~xjR0Q-Da@;BdN6A zyY{!uAan1v|4>Ui!_oFmBtF>GsF0@;($K$gQB-pnQuX#=hw?qT>U(8Tk;oX+v)KvcP6D`R@qmOTP%I+%YfYr2%wVlU zYrDqQsVukUA>fQT*DxJ7OM!#su;8QF8*KF(UfWO%UTg%yc!$Z;x1jaikG6B?x9*!K z7GFGB_dRQ#2~wTXO<3k>>&uAY=7<~l5E+eWjDo!JTdowAl-?86DBV!31TtaN>GK0+ zPpW307k~s)fdZ4z0|(~uRH8SMZIvz1r@uyb9w#jKw#O0PEoYVnstM0FH8vaP7Im`O z?k=hUkh#DDcc{WX4pt(I7uxY4&Vhx^?YQG?QdGsyeWJHRH6AGIubnM-hkRj z@^6L;K%Xlwsik^NQz`_e-awK(vL&8nV}0g)b?)C|xW^*quYvHl;`vjxV>v*eUMEAzMOxY<_I^q67^h=UTY&SQ|MFZ5nkqu z{T|F4S2Cd7b>n@Z}M0`IR20RPsxUV1GoVU4k)VViG346{{9_@!@sZ$E`r2dYJh z5xp4ZCk*uM=@S`Ao4o0-m$T-#K6Ktmr-vx4qrbr@U;V<;^pW`D?f&=f=d#=RKZYF- zO@t)~vW!Fnxq(g4z;AtNB}1ScrO`357W^Vopa%t_c{*XqIP@SoplGNn3GM6)K}K;< zYXh%GC?=^rBDU;4ahLe27$Ou0n5O=H8e_h*8zkcX3`T?mR%tv?0`8>jnnA)^+?xb! z>csos4v*&`vD#vygaCtkBm4uW7S$8+zO~C)c%R9I*#=iNDXF&{!IMuaG~U;^G7|aC zNe7P-kF8juMVR+PBz?BKlZEBZmv{k+fy2X}Nwhf;csy(Tqx4H4*zJuOb)w+O-wz}SrgZ=OI`rV{NX^k2Kv}xDAqDVUO!Rdb zx66!tuF<Nh(dhTtzzIt2(i1;s)h|za6L# z4}UzG!o1E4zG4L~h&F-O2VJF=Zmv^m4&ND<+%Gt!34kUzeiyWIMc`}XrSut{kh-#AHr1ok!f@$#P6bghoTR>Uc7+YQy;9)P zsT>0T@G2`=UL`IhBFRQU!7bUg z6P{EcWS%&#cSvE(OInU$&A09Ma!MZReK=7rHa}f)JI@f|{bG{s>g_^JO(I@J zp_HpbUHHhmK8vnBHW$JC!@?(wB>-8*pBB!_I#dyXvu(!vIfD*GHw=Gl3Vw+2lbYmA z5|l?`1ly#T8B?vf9I3K5?_A1^v}vS!EiW|8bY_3IPJEUvi#Y-m%CWdIc$}{xX191- z`e)16j&J7)ZldLB^Nc2wa?!ebX8sa-KV_>@_QBKTUQdqI3QfI0jT`9$tUXW7o7?tI z6c*f_oA$4l{eMLMX5t*0;KvGNubTdF6f8LSE*x#W8+`mIT?y!?BOg*gTI-3tVGFI5 z!Z^`eeA6c%z~#Xni)S5q;GhkWIb78!s@WVbpn;li5^3WD-#(hPG@#fd>#g{G_KkcX z;cVN8yVPXaNQ7bLgtR(!=6FP(0UYx>`4x!I|67CKOd2*l>DGYJK$}!|hOlHWmAClY zlq*7Zu(In-)Z29+*xq5vYF6QPB{Xd5BBMYl=jXyfr#Y*kn;ZZeJ;CrRiVk#}Twk#c zSTzc!Q^&5~`;2OKvKnZ)JXSU$DITrk;t;%Gy4oo~7H93a7r=yD=8fI%9x9{+2)Ldm zH>>-o>hU_ib-RY1By63Zw4 zr~QfmEGd2+=Cgf1djjp;)`%IEq@Z-6>1|HwNyHFfyy73I^{FG<<>4ny!AVV%M8u>* zc$T#W>!4xk0rw_bsb;5h>o@cUHl?VY4b}#?3CDOJHEWouF_FeLhTyE!w)I`%wx?m= zi@VuJe>HwHmJ@#wT5`b59~x|z;C@W?5E^ITT;*bsG9EJFhf6DVdvX+NEu-1>%M;y?xx-SO1hwEYRz6D+W z`B7RRrZ5q^m;>Nvi9vf95wQyTj_r|oXvLDu)R)ofnZljna`#oje%ID`$1xuDtC)(2 zNz!z}wI*UDGp18M^KuAZ{G!c%kqKvi|C-+10+kPhDys z(BZ_?QEwRT=;b8POQDS~=CEj^_zVS7!u=>VVSkcF7$!K9r4^PKrfC|w*_c&M%xc@1 zQAjUj^|`x8&BF+djH>))EVzgR#Ktgk`*Qg5Wmk@RfwrhUAPFASV_Nj23vK-!A@D7% zI+>Brf7ys`{Mtvo@Ni%HBcdt^p5$pLoH_x>y_(sp0GQ=b`EAn6Wy^{N!UlyY2)#YG zxkaP7;WYh~%dyy_af`-j$H-_%i1qnC={k?TXDFr!-Gm1KCz4C$iL?gVIv z&Q3Mxp76h7`f!uPVu2;AvorYo^s|#D?VaiMDW6gnK$LGiX&A=$j&%uX-pZ9={V=M{ z#okpvKoguya)`l+g}v`P5ilQNI_%fo$fev*6P}0qhdg(5_Fj z8d?EF<^wgNm6IA5_XU5{OE=ptuGRajRyo>>2uIc(Z)4HxkLTb4WOxMM+g^qPzlUsV z?2p{r2uJxVW@=QQNIS-Zs4ezjb2{jS&Qpjak8p(u!tq_MVF38CM>#v}bc9iw4~mqO zl`0cx_3WJqQH0W46k6UdzMT32K!M7&hBzPAm6o8D?37#Ufbo=vmT-1PNfyrN?2{ak zf5**JCxH&4)+%n7$0-OYIm#<<=_~MqxeZR*#cgjWlM-J)n+YL}*NUk*$*0$=bpnDq zwr?{HYP~@LXz_e-Lsf(>DlRH5dZc+jrFpb9+E2tNa>oz2*UgBmAWHg6R&kH79JW%$ zW3tV~dn|fx7yZt3!Fy=yBho8CQU_=i{?O>M9Mlzh03(nJA8FwqCZ0<>S&ixD-Y=6L zH$`$k=Rk42P%-feuFis+M{)pVO%tE>9!^M=d!0K!+MT|YxOgjh2G-O)?HA=Yfs%SU zbz=uv;skrk^7jvre)W&(M(fJB;ag%)t>_pD<_;$4&kY9U&B)gNE7 z2j4qY8?~h-P$%G;#!`X4O^kxs#*s^q%8Rd8JQzvdmKoKC|d&5uA_yXhT zrRm%-0Yd5}y6QSQD+x5f14dB++!}(Zc2J=dB46e6T=d#;Qbu(LZ^u2<_Q!t?NG?S1 z_6$X@og`&E@8F$!Hvrt$!#QpC zP-rWvG#>uk?}_-Y31qDHCoekVqc74hz@M{vszkJ*zx(n_K!UB7^i*UeXA8lxEAXuL z-Mndrj^ zxyL7N)V&AZ;9qv-K}d;IW35cdjAyVu4}bECu3jP;G*S@$EZkW+{V&r`}6@hmAa z&%0-`{vAS~)f(7gEW3Z$#fB%X(vY$T(#~`%DqQ(BuK;>wIogWXASOdH>u8kU^E!Ue z78kU;{A+2n4x$w>uu)qwQ7`5 zJJ|c+AXEeMn4h9HI7C~^OqJ-R$bEYm)B6KshV6uJQ4Ng>U~1-|;uj zvz_4E7y@|~1|Ts@2lMGJ5p0J0O^~lQ#2Ri~PgcU*pMjs}olConeORZ3u zYD+dM1DkD$jkp0=Cd5Ep922;d-x-F%j!;iWiHlsgBdFNfSZ3ju5%ubxY}K7~Kvo1w ztL=MAQixm{JU|WDq{`D5kgw{zhhp*}Sth;RZ}2LF>W@^AY~3FWs5!vduxW&-XLjiM zwM9~4G88~5;t0qv2=WZ+nh((>MLe1@`GQ$y35`{@lzD{X3j&!>P9p!P02nR;wv zoSzQzIA@G}r8`0W`QSMFE6xGskGJfFvP`;h9m3u2bAipAr5F4&CQHn0HDPbY>`6qV z-?XK9c@gYDUOe+nn1M*D?Aoe*o%){&oe@J<_OS);YsHXSX+&*4I=l*9jY_Y$+<*hXAuE@ zn=TPe{$hKXCtXHQ)8{q=)>P*CsYvD$AcJwsok1I4<9yrAhLot1Kn^3$8eOA5NL*Z! zuD+leC1=(?7WtC%6EnAeFGbf8m883%98P+Er8_Vl`ceU`t)Va5Mz+Cl-+MBoLeI6~ z&bErXGfb`e0}Cp^wDVr@9;`Md5$4ffLU@vMc_+PUucmQOOA%)QWv6E%XWvqeJr|DG z%iuv;aO}UN&&kL3-fZyryP^ z$cPJQCJH7hYgm6y2sifMKRLllTelB;mFeXRSjyo}>cW@+Rq8N%;1b3~doRzWHfsKOmpYG)pf~3v^I-${PcLWZqlQUUBs-%X)JS>0#Je2KDFZLvmtpfuP7#y#n8j>ZlA~; zik#Y*sXiqPrip;|h}#_HUy6hE(ps2t%;JtfiZzhsU1~D}LXc2QSSG~a%CGM4rx#aC z8ke(Ael>~Zo3cmzBpOsBY~tjoTwM`2H%}egi<7j{%}$HPI=DCg?B@^Tm4GJ%5U?;& zU~v;7%h|;X2f4jD)4JijJ-(jW4rA3 Zt#D4oNKPwk<*K!O4Q;!AW?@DqLa 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 Date: Wed, 8 Feb 2023 22:03:05 +0800 Subject: [PATCH 056/281] setting up deadline for 3dsmax --- openpype/hosts/max/api/lib.py | 33 +++++ openpype/hosts/max/api/lib_renderproducts.py | 102 +++++++++++++ openpype/hosts/max/api/lib_rendersettings.py | 125 ++++++++++++++++ .../hosts/max/plugins/create/create_render.py | 33 +++++ .../max/plugins/publish/collect_render.py | 72 +++++++++ .../maya/plugins/publish/collect_render.py | 2 - .../plugins/publish/submit_3dmax_deadline.py | 137 ++++++++++++++++++ .../defaults/project_settings/max.json | 7 + .../schemas/projects_schema/schema_main.json | 4 + .../projects_schema/schema_project_max.json | 52 +++++++ 10 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/max/api/lib_renderproducts.py create mode 100644 openpype/hosts/max/api/lib_rendersettings.py create mode 100644 openpype/hosts/max/plugins/create/create_render.py create mode 100644 openpype/hosts/max/plugins/publish/collect_render.py create mode 100644 openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py create mode 100644 openpype/settings/defaults/project_settings/max.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_max.json diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 9256ca9ac1..8c421b2f9b 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -120,3 +120,36 @@ def get_all_children(parent, node_type=None): return ([x for x in child_list if rt.superClassOf(x) == node_type] if node_type else child_list) + + +def get_current_renderer(): + """get current renderer""" + return rt.renderers.production + + +def get_default_render_folder(project_setting=None): + return (project_setting["max"] + ["RenderSettings"] + ["default_render_image_folder"] + ) + + +def set_framerange(startFrame, endFrame): + """Get/set the type of time range to be rendered. + + Possible values are: + + 1 -Single frame. + + 2 -Active time segment ( animationRange ). + + 3 -User specified Range. + + 4 -User specified Frame pickup string (for example "1,3,5-12"). + """ + # hard-code, there should be a custom setting for this + rt.rendTimeType = 4 + if startFrame is not None and endFrame is not None: + frameRange = "{0}-{1}".format(startFrame, endFrame) + rt.rendPickupFrames = frameRange + diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py new file mode 100644 index 0000000000..f3bb8bdad1 --- /dev/null +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -0,0 +1,102 @@ +# Render Element Example : For scanline render, VRay +# https://help.autodesk.com/view/MAXDEV/2022/ENU/?guid=GUID-E8F75D47-B998-4800-A3A5-610E22913CFC +# arnold +# https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_3ds_max_ax_maxscript_commands_ax_renderview_commands_html +import os +from pymxs import runtime as rt +from openpype.hosts.max.api.lib import ( + get_current_renderer, + get_default_render_folder +) +from openpype.pipeline.context_tools import get_current_project_asset +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io + + +class RenderProducts(object): + + @classmethod + def __init__(self, project_settings=None): + self._project_settings = project_settings + if not self._project_settings: + self._project_settings = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + + def render_product(self, container): + folder = rt.maxFilePath + folder = folder.replace("\\", "/") + setting = self._project_settings + render_folder = get_default_render_folder(setting) + + output_file = os.path.join(folder, render_folder, container) + context = get_current_project_asset() + startFrame = context["data"].get("frameStart") + endFrame = context["data"].get("frameEnd") + 1 + + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + full_render_list = self.beauty_render_product(output_file, + startFrame, + endFrame, + img_fmt) + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + + if renderer == "VUE_File_Renderer": + return full_render_list + + if ( + renderer == "ART_Renderer" or + renderer == "Redshift Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer" + ): + render_elem_list = self.render_elements_product(output_file, + startFrame, + endFrame, + img_fmt) + for render_elem in render_elem_list: + full_render_list.append(render_elem) + return full_render_list + + if renderer == "Arnold": + return full_render_list + + + def beauty_render_product(self, folder, startFrame, endFrame, fmt): + # get the beauty + beauty_frame_range = list() + + for f in range(startFrame, endFrame): + beauty = "{0}.{1}.{2}".format(folder, str(f), fmt) + beauty = beauty.replace("\\", "/") + beauty_frame_range.append(beauty) + + return beauty_frame_range + + # TODO: Get the arnold render product + def render_elements_product(self, folder, startFrame, endFrame, fmt): + """Get all the render element output files. """ + render_dirname = list() + + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + # get render elements from the renders + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + + render_dir = os.path.join(folder, renderpass) + if renderlayer_name.enabled: + for f in range(startFrame, endFrame): + render_element = "{0}.{1}.{2}".format(render_dir, str(f), fmt) + render_element = render_element.replace("\\", "/") + render_dirname.append(render_element) + + return render_dirname + + def image_format(self): + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + return img_fmt diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py new file mode 100644 index 0000000000..8c8a82ae66 --- /dev/null +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -0,0 +1,125 @@ +import os +from pymxs import runtime as rt +from openpype.lib import Logger +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io +from openpype.pipeline.context_tools import get_current_project_asset + +from openpype.hosts.max.api.lib import ( + set_framerange, + get_current_renderer, + get_default_render_folder +) + + +class RenderSettings(object): + + log = Logger.get_logger("RenderSettings") + + _aov_chars = { + "dot": ".", + "dash": "-", + "underscore": "_" + } + + @classmethod + def __init__(self, project_settings=None): + self._project_settings = project_settings + if not self._project_settings: + self._project_settings = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + + def set_render_camera(self, selection): + for sel in selection: + # to avoid Attribute Error from pymxs wrapper + found = False + if rt.classOf(sel) in rt.Camera.classes: + found = True + rt.viewport.setCamera(sel) + break + if not found: + raise RuntimeError("Camera not found") + + + def set_renderoutput(self, container): + folder = rt.maxFilePath + # hard-coded, should be customized in the setting + folder = folder.replace("\\", "/") + # hard-coded, set the renderoutput path + setting = self._project_settings + render_folder = get_default_render_folder(setting) + output_dir = os.path.join(folder, render_folder) + if not os.path.exists(output_dir): + os.makedirs(output_dir) + # hard-coded, should be customized in the setting + context = get_current_project_asset() + + # get project reoslution + width = context["data"].get("resolutionWidth") + height = context["data"].get("resolutionHeight") + # Set Frame Range + startFrame = context["data"].get("frameStart") + endFrame = context["data"].get("frameEnd") + set_framerange(startFrame, endFrame) + # get the production render + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + output = os.path.join(output_dir, container) + try: + aov_separator = self._aov_chars[( + self._project_settings["maya"] + ["RenderSettings"] + ["aov_separator"] + )] + except KeyError: + aov_separator = "." + outputFilename = "{0}.{1}".format(output, img_fmt) + outputFilename = outputFilename.replace("{aov_separator}", aov_separator) + rt.rendOutputFilename = outputFilename + if renderer == "VUE_File_Renderer": + return + # TODO: Finish the arnold render setup + if renderer == "Arnold": + return + + if ( + renderer == "ART_Renderer" or + renderer == "Redshift Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer" + ): + self.render_element_layer(output, width, height, img_fmt) + + rt.rendSaveFile= True + + + def render_element_layer(self, dir, width, height, ext): + """For Renderers with render elements""" + rt.renderWidth = width + rt.renderHeight = height + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 0: + return + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + render_element = os.path.join(dir, renderpass) + aov_name = "{0}.{1}".format(render_element, ext) + try: + aov_separator = self._aov_chars[( + self._project_settings["maya"] + ["RenderSettings"] + ["aov_separator"] + )] + except KeyError: + aov_separator = "." + + aov_name = aov_name.replace("{aov_separator}", aov_separator) + render_elem.SetRenderElementFileName(i, aov_name) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py new file mode 100644 index 0000000000..76c10ca4a9 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating camera.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.hosts.max.api.lib_rendersettings import RenderSettings + + +class CreateRender(plugin.MaxCreator): + identifier = "io.openpype.creators.max.render" + label = "Render" + family = "maxrender" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + sel_obj = list(rt.selection) + instance = super(CreateRender, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + container_name = instance.data.get("instance_node") + container = rt.getNodeByName(container_name) + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + for obj in sel_obj: + obj.parent = container + # for additional work on the node: + # instance_node = rt.getNodeByName(instance.get("instance_node")) + + # set viewport camera for rendering(mandatory for deadline) + RenderSettings().set_render_camera(sel_obj) + # set output paths for rendering(mandatory for deadline) + RenderSettings().set_renderoutput(container_name) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py new file mode 100644 index 0000000000..fc44c01206 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +"""Collect Render""" +import os +import pyblish.api + +from pymxs import runtime as rt +from openpype.pipeline import legacy_io +from openpype.hosts.max.api.lib import get_current_renderer +from openpype.hosts.max.api.lib_renderproducts import RenderProducts + + +class CollectRender(pyblish.api.InstancePlugin): + """Collect Render for Deadline""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect 3dmax Render Layers" + hosts = ['max'] + families = ["maxrender"] + + def process(self, instance): + context = instance.context + folder = rt.maxFilePath + file = rt.maxFileName + current_file = os.path.join(folder, file) + filepath = current_file.replace("\\", "/") + + context.data['currentFile'] = current_file + asset = legacy_io.Session["AVALON_ASSET"] + + render_layer_files = RenderProducts().render_product(instance.name) + folder = folder.replace("\\", "/") + + imgFormat = RenderProducts().image_format() + renderer_class = get_current_renderer() + renderer_name = str(renderer_class).split(":")[0] + # setup the plugin as 3dsmax for the internal renderer + if ( + renderer_name == "ART_Renderer" or + renderer_name == "Default_Scanline_Renderer" or + renderer_name == "Quicksilver_Hardware_Renderer" + ): + plugin = "3dsmax" + + if ( + renderer_name == "V_Ray_6_Hotfix_3" or + renderer_name == "V_Ray_GPU_6_Hotfix_3" + ): + plugin = "Vray" + + if renderer_name == "Redshift Renderer": + plugin = "redshift" + + if renderer_name == "Arnold": + plugin = "arnold" + + # https://forums.autodesk.com/t5/3ds-max-programming/pymxs-quickrender-animation-range/td-p/11216183 + + data = { + "subset": instance.name, + "asset": asset, + "publish": True, + "imageFormat": imgFormat, + "family": 'maxrender', + "families": ['maxrender'], + "source": filepath, + "files": render_layer_files, + "plugin": plugin, + "frameStart": context.data['frameStart'], + "frameEnd": context.data['frameEnd'] + } + self.log.info("data: {0}".format(data)) + instance.data.update(data) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index b1ad3ca58e..c5fce219fa 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -184,7 +184,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin): self.log.info("multipart: {}".format( multipart)) assert exp_files, "no file names were generated, this is bug" - self.log.info(exp_files) # if we want to attach render to subset, check if we have AOV's # in expectedFiles. If so, raise error as we cannot attach AOV @@ -320,7 +319,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ) } - # Collect Deadline url if Deadline module is enabled deadline_settings = ( context.data["system_settings"]["modules"]["deadline"] diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py new file mode 100644 index 0000000000..7e7173e4ce --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -0,0 +1,137 @@ +import os +import json +import getpass + +import requests +import pyblish.api + + +from openpype.pipeline import legacy_io + + +class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): + """ + 3DMax File Submit Render Deadline + + """ + + label = "Submit 3DsMax Render to Deadline" + order = pyblish.api.IntegratorOrder + hosts = ["max"] + families = ["maxrender"] + targets = ["local"] + + def process(self, instance): + context = instance.context + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) + comment = context.data.get("comment", "") + deadline_user = context.data.get("deadlineUser", getpass.getuser()) + jobname ="{0} - {1}".format(filename, instance.name) + + # StartFrame to EndFrame + frames = "{start}-{end}".format( + start=int(instance.data["frameStart"]), + end=int(instance.data["frameEnd"]) + ) + + payload = { + "JobInfo": { + # Top-level group name + "BatchName": filename, + + # Job name, as seen in Monitor + "Name": jobname, + + # Arbitrary username, for visualisation in Monitor + "UserName": deadline_user, + + "Plugin": instance.data["plugin"], + "Pool": instance.data.get("primaryPool"), + "secondaryPool": instance.data.get("secondaryPool"), + "Frames": frames, + "ChunkSize" : instance.data.get("chunkSize", 10), + "Comment": comment + }, + "PluginInfo": { + # Input + "SceneFile": instance.data["source"], + "Version": "2023", + "SaveFile" : True, + # Mandatory for Deadline + # Houdini version without patch number + + "IgnoreInputs": True + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + # Include critical environment variables with submission + api.Session + keys = [ + # Submit along the current Avalon tool setup that we launched + # this application with so the Render Slave can build its own + # similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9" + "AVALON_TOOLS", + "OPENPYPE_VERSION" + ] + # Add mongo url if it's enabled + if context.data.get("deadlinePassMongoUrl"): + keys.append("OPENPYPE_MONGO") + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + # Include OutputFilename entries + # The first entry also enables double-click to preview rendered + # frames from Deadline Monitor + output_data = {} + # need to be fixed + for i, filepath in enumerate(instance.data["files"]): + dirname = os.path.dirname(filepath) + fname = os.path.basename(filepath) + output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/") + output_data["OutputFilename%d" % i] = fname + + if not os.path.exists(dirname): + self.log.info("Ensuring output directory exists: %s" % + dirname) + os.makedirs(dirname) + + payload["JobInfo"].update(output_data) + + self.submit(instance, payload) + + def submit(self, instance, payload): + + context = instance.context + deadline_url = context.data.get("defaultDeadline") + deadline_url = instance.data.get( + "deadlineUrl", deadline_url) + + assert deadline_url, "Requires Deadline Webservice URL" + + plugin = payload["JobInfo"]["Plugin"] + self.log.info("Using Render Plugin : {}".format(plugin)) + + self.log.info("Submitting..") + self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) + + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(deadline_url) + response = requests.post(url, json=payload, verify=False) + if not response.ok: + raise Exception(response.text) + # Store output dir for unified publisher (filesequence) + expected_files = instance.data["files"] + self.log.info("exp:{}".format(expected_files)) + output_dir = os.path.dirname(expected_files[0]) + instance.data["outputDir"] = output_dir + instance.data["deadlineSubmissionJob"] = response.json() diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json new file mode 100644 index 0000000000..651a074a08 --- /dev/null +++ b/openpype/settings/defaults/project_settings/max.json @@ -0,0 +1,7 @@ +{ + "RenderSettings": { + "default_render_image_folder": "renders/max", + "aov_separator": "underscore", + "image_format": "exr" + } +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 0b9fbf7470..ebe59c7942 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -82,6 +82,10 @@ "type": "schema", "name": "schema_project_slack" }, + { + "type": "schema", + "name": "schema_project_max" + }, { "type": "schema", "name": "schema_project_maya" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json new file mode 100644 index 0000000000..3d4cd5c54a --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -0,0 +1,52 @@ +{ + "type": "dict", + "collapsible": true, + "key": "max", + "label": "Max", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "RenderSettings", + "label": "Render Settings", + "children": [ + { + "type": "text", + "key": "default_render_image_folder", + "label": "Default render image folder" + }, + { + "key": "aov_separator", + "label": "AOV Separator character", + "type": "enum", + "multiselection": false, + "default": "underscore", + "enum_items": [ + {"dash": "- (dash)"}, + {"underscore": "_ (underscore)"}, + {"dot": ". (dot)"} + ] + }, + { + "key": "image_format", + "label": "Output Image Format", + "type": "enum", + "multiselection": false, + "defaults": "exr", + "enum_items": [ + {"avi": "avi"}, + {"bmp": "bmp"}, + {"exr": "exr"}, + {"tif": "tif"}, + {"tiff": "tiff"}, + {"jpg": "jpg"}, + {"png": "png"}, + {"tga": "tga"}, + {"dds": "dds"} + ] + } + ] + } + ] +} \ No newline at end of file From 1e5ec12070c36da7dc16c0eb2fbf0646424eb4ed Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 Feb 2023 22:23:47 +0800 Subject: [PATCH 057/281] hound fix --- openpype/hosts/max/api/lib.py | 6 ++---- openpype/hosts/max/api/lib_renderproducts.py | 15 +++++++++------ openpype/hosts/max/api/lib_rendersettings.py | 18 +++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 8c421b2f9b..0477b43182 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -130,8 +130,7 @@ def get_current_renderer(): def get_default_render_folder(project_setting=None): return (project_setting["max"] ["RenderSettings"] - ["default_render_image_folder"] - ) + ["default_render_image_folder"]) def set_framerange(startFrame, endFrame): @@ -147,9 +146,8 @@ def set_framerange(startFrame, endFrame): 4 -User specified Frame pickup string (for example "1,3,5-12"). """ - # hard-code, there should be a custom setting for this + # hard-code, there should be a custom setting for this rt.rendTimeType = 4 if startFrame is not None and endFrame is not None: frameRange = "{0}-{1}".format(startFrame, endFrame) rt.rendPickupFrames = frameRange - diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index f3bb8bdad1..3b7767478d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -34,7 +34,7 @@ class RenderProducts(object): startFrame = context["data"].get("frameStart") endFrame = context["data"].get("frameEnd") + 1 - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa full_render_list = self.beauty_render_product(output_file, startFrame, endFrame, @@ -52,7 +52,7 @@ class RenderProducts(object): renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or renderer == "Quicksilver_Hardware_Renderer" - ): + ): render_elem_list = self.render_elements_product(output_file, startFrame, endFrame, @@ -64,13 +64,14 @@ class RenderProducts(object): if renderer == "Arnold": return full_render_list - def beauty_render_product(self, folder, startFrame, endFrame, fmt): # get the beauty beauty_frame_range = list() for f in range(startFrame, endFrame): - beauty = "{0}.{1}.{2}".format(folder, str(f), fmt) + beauty = "{0}.{1}.{2}".format(folder, + str(f), + fmt) beauty = beauty.replace("\\", "/") beauty_frame_range.append(beauty) @@ -83,7 +84,7 @@ class RenderProducts(object): render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() - # get render elements from the renders + # get render elements from the renders for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") @@ -91,7 +92,9 @@ class RenderProducts(object): render_dir = os.path.join(folder, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}.{1}.{2}".format(render_dir, str(f), fmt) + render_element = "{0}.{1}.{2}".format(render_dir, + str(f), + fmt) render_element = render_element.replace("\\", "/") render_dirname.append(render_element) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 8c8a82ae66..11dd005ad7 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -41,7 +41,6 @@ class RenderSettings(object): if not found: raise RuntimeError("Camera not found") - def set_renderoutput(self, container): folder = rt.maxFilePath # hard-coded, should be customized in the setting @@ -66,7 +65,7 @@ class RenderSettings(object): renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa output = os.path.join(output_dir, container) try: aov_separator = self._aov_chars[( @@ -77,7 +76,8 @@ class RenderSettings(object): except KeyError: aov_separator = "." outputFilename = "{0}.{1}".format(output, img_fmt) - outputFilename = outputFilename.replace("{aov_separator}", aov_separator) + outputFilename = outputFilename.replace("{aov_separator}", + aov_separator) rt.rendOutputFilename = outputFilename if renderer == "VUE_File_Renderer": return @@ -92,11 +92,10 @@ class RenderSettings(object): renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or renderer == "Quicksilver_Hardware_Renderer" - ): + ): self.render_element_layer(output, width, height, img_fmt) - rt.rendSaveFile= True - + rt.rendSaveFile = True def render_element_layer(self, dir, width, height, ext): """For Renderers with render elements""" @@ -115,11 +114,12 @@ class RenderSettings(object): try: aov_separator = self._aov_chars[( self._project_settings["maya"] - ["RenderSettings"] - ["aov_separator"] + ["RenderSettings"] + ["aov_separator"] )] except KeyError: aov_separator = "." - aov_name = aov_name.replace("{aov_separator}", aov_separator) + aov_name = aov_name.replace("{aov_separator}", + aov_separator) render_elem.SetRenderElementFileName(i, aov_name) From 0ea62e664f5f0c54c5593147f69ae6740e07380b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 Feb 2023 22:29:28 +0800 Subject: [PATCH 058/281] hound fix --- openpype/hosts/max/api/lib_rendersettings.py | 6 +++--- openpype/hosts/max/plugins/publish/collect_render.py | 3 ++- .../deadline/plugins/publish/submit_3dmax_deadline.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 11dd005ad7..90398e841c 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -35,9 +35,9 @@ class RenderSettings(object): # to avoid Attribute Error from pymxs wrapper found = False if rt.classOf(sel) in rt.Camera.classes: - found = True - rt.viewport.setCamera(sel) - break + found = True + rt.viewport.setCamera(sel) + break if not found: raise RuntimeError("Camera not found") diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index fc44c01206..cda774bf11 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -53,7 +53,8 @@ class CollectRender(pyblish.api.InstancePlugin): if renderer_name == "Arnold": plugin = "arnold" - # https://forums.autodesk.com/t5/3ds-max-programming/pymxs-quickrender-animation-range/td-p/11216183 + # https://forums.autodesk.com/t5/3ds-max-programming/ + # pymxs-quickrender-animation-range/td-p/11216183 data = { "subset": instance.name, diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index 7e7173e4ce..faeb071524 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -27,7 +27,7 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): filename = os.path.basename(filepath) comment = context.data.get("comment", "") deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname ="{0} - {1}".format(filename, instance.name) + jobname = "{0} - {1}".format(filename, instance.name) # StartFrame to EndFrame frames = "{start}-{end}".format( @@ -50,14 +50,14 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): "Pool": instance.data.get("primaryPool"), "secondaryPool": instance.data.get("secondaryPool"), "Frames": frames, - "ChunkSize" : instance.data.get("chunkSize", 10), + "ChunkSize": instance.data.get("chunkSize", 10), "Comment": comment }, "PluginInfo": { # Input "SceneFile": instance.data["source"], "Version": "2023", - "SaveFile" : True, + "SaveFile": True, # Mandatory for Deadline # Houdini version without patch number @@ -67,7 +67,7 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): # Mandatory for Deadline, may be empty "AuxFiles": [] } - # Include critical environment variables with submission + api.Session + # Include critical environment variables with submission + api.Session keys = [ # Submit along the current Avalon tool setup that we launched # this application with so the Render Slave can build its own From 6a55e2a9a3472214c077a66954ce50d1665b0bfa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 Feb 2023 22:33:10 +0800 Subject: [PATCH 059/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- openpype/hosts/max/plugins/publish/collect_render.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 3b7767478d..a7361a5a25 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -101,5 +101,5 @@ class RenderProducts(object): return render_dirname def image_format(self): - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa return img_fmt diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index cda774bf11..dd85afd586 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -53,9 +53,6 @@ class CollectRender(pyblish.api.InstancePlugin): if renderer_name == "Arnold": plugin = "arnold" - # https://forums.autodesk.com/t5/3ds-max-programming/ - # pymxs-quickrender-animation-range/td-p/11216183 - data = { "subset": instance.name, "asset": asset, From b1ef1b751f5bdf3df1274b524ac55f34f4b1c51d Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 19:11:20 +0100 Subject: [PATCH 060/281] Abstract get_representation_path function and use it on shotgrid to fix remote errors with data instances not having 'published_path' --- .../publish/integrate_ftrack_instances.py | 52 ++----------------- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 48 ++++++++++++----- .../plugins/publish/integrate_slack_api.py | 11 ++-- openpype/plugins/publish/integrate.py | 46 ++++++++++++++++ 5 files changed, 92 insertions(+), 69 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 2d06e2ab02..c3baecec67 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -10,6 +10,7 @@ from openpype.lib.transcoding import ( ) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.transcoding import VIDEO_EXTENSIONS +from openpype.plugins.publish.integrate import get_representation_path class IntegrateFtrackInstance(pyblish.api.InstancePlugin): @@ -153,7 +154,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if not review_representations or has_movie_review: for repre in thumbnail_representations: - repre_path = self._get_repre_path(instance, repre, False) + repre_path = get_representation_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -210,7 +211,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "from {}".format(repre)) continue - repre_path = self._get_repre_path(instance, repre, False) + repre_path = get_representation_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -324,7 +325,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Add others representations as component for repre in other_representations: - published_path = self._get_repre_path(instance, repre, True) + published_path = get_representation_path(instance, repre, True) if not published_path: continue # Create copy of base comp item and append it @@ -364,51 +365,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): def _collect_additional_metadata(self, streams): pass - def _get_repre_path(self, instance, repre, only_published): - """Get representation path that can be used for integration. - - When 'only_published' is set to true the validation of path is not - relevant. In that case we just need what is set in 'published_path' - as "reference". The reference is not used to get or upload the file but - for reference where the file was published. - - Args: - instance (pyblish.Instance): Processed instance object. Used - for source of staging dir if representation does not have - filled it. - repre (dict): Representation on instance which could be and - could not be integrated with main integrator. - only_published (bool): Care only about published paths and - ignore if filepath is not existing anymore. - - Returns: - str: Path to representation file. - None: Path is not filled or does not exists. - """ - - published_path = repre.get("published_path") - if published_path: - published_path = os.path.normpath(published_path) - if os.path.exists(published_path): - return published_path - - if only_published: - return published_path - - comp_files = repre["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] - else: - filename = comp_files - - staging_dir = repre.get("stagingDir") - if not staging_dir: - staging_dir = instance.data["stagingDir"] - src_path = os.path.normpath(os.path.join(staging_dir, filename)) - if os.path.exists(src_path): - return src_path - return None - def _get_asset_version_status_name(self, instance): if not self.asset_versions_status_profiles: return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index cfd2d10fd9..ee6ece2e67 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -1,6 +1,8 @@ import os import pyblish.api +from openpype.plugins.publish.integrate import get_representation_path + class IntegrateShotgridPublish(pyblish.api.InstancePlugin): """ @@ -22,7 +24,7 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") + local_path = get_representation_path(instance, representation, False) code = os.path.basename(local_path) if representation.get("tags", []): diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index a1b7140e22..60ad1ff91d 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -1,6 +1,7 @@ -import os import pyblish.api +from openpype.plugins.publish.integrate import get_representation_path + class IntegrateShotgridVersion(pyblish.api.InstancePlugin): """Integrate Shotgrid Version""" @@ -17,15 +18,37 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): # TODO: Use path template solver to build version code from settings anatomy = instance.data.get("anatomyData", {}) - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) + ### Starts Alkemy-X Override ### + # code = "_".join( + # [ + # anatomy["project"]["code"], + # anatomy["parent"], + # anatomy["asset"], + # anatomy["task"]["name"], + # "v{:03}".format(int(anatomy["version"])), + # ] + # ) + # Initial editorial Shotgrid versions don't need task in name + if anatomy["app"] == "hiero": + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + else: + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + ### Ends Alkemy-X Override ### version = self._find_existing_version(code, context) @@ -41,8 +64,9 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") - code = os.path.basename(local_path) + # Get representation path from published_path or create it from stagingDir if not existent + local_path = get_representation_path(instance, representation, False) + self.log.info("Local path: %s", local_path) if "shotgridreview" in representation.get("tags", []): diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 612031efac..ac918381c0 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -9,6 +9,7 @@ import time from openpype.client import OpenPypeMongoConnection from openpype.lib.plugin_tools import prepare_template_data +from openpype.plugins.publish.integrate import get_representation_path class IntegrateSlackAPI(pyblish.api.InstancePlugin): @@ -167,10 +168,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = ( - repre.get("published_path") or - os.path.join(repre["stagingDir"], repre["files"]) - ) + repre_thumbnail_path = get_representation_path(instance, repre, False) if os.path.exists(repre_thumbnail_path): thumbnail_path = repre_thumbnail_path break @@ -184,10 +182,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = ( - repre.get("published_path") or - os.path.join(repre["stagingDir"], repre["files"]) - ) + repre_review_path = get_representation_path(instance, repre, False) if os.path.exists(repre_review_path): review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7b73943c37..854cf8b9ec 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -53,6 +53,52 @@ def get_frame_padded(frame, padding): return "{frame:0{padding}d}".format(padding=padding, frame=frame) +def get_representation_path(instance, repre, only_published): + """Get representation path that can be used for integration. + + When 'only_published' is set to true the validation of path is not + relevant. In that case we just need what is set in 'published_path' + as "reference". The reference is not used to get or upload the file but + for reference where the file was published. + + Args: + instance (pyblish.Instance): Processed instance object. Used + for source of staging dir if representation does not have + filled it. + repre (dict): Representation on instance which could be and + could not be integrated with main integrator. + only_published (bool): Care only about published paths and + ignore if filepath is not existing anymore. + + Returns: + str: Path to representation file. + None: Path is not filled or does not exists. + """ + + published_path = repre.get("published_path") + if published_path: + published_path = os.path.normpath(published_path) + if os.path.exists(published_path): + return published_path + + if only_published: + return published_path + + comp_files = repre["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + staging_dir = repre.get("stagingDir") + if not staging_dir: + staging_dir = instance.data["stagingDir"] + src_path = os.path.normpath(os.path.join(staging_dir, filename)) + if os.path.exists(src_path): + return src_path + return None + + class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. From c13416b68570365b9ce3b247979f79f721cc3031 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 19:39:47 +0100 Subject: [PATCH 061/281] Remove unintended code block and fix Hound lint warnings for <79 chars width --- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 46 +++++-------------- .../plugins/publish/integrate_slack_api.py | 8 +++- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index ee6ece2e67..7789a47074 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -24,7 +24,9 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = get_representation_path(instance, representation, False) + local_path = get_representation_path( + instance, representation, False + ) code = os.path.basename(local_path) if representation.get("tags", []): diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index 60ad1ff91d..94fc4ae9e8 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -18,37 +18,15 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): # TODO: Use path template solver to build version code from settings anatomy = instance.data.get("anatomyData", {}) - ### Starts Alkemy-X Override ### - # code = "_".join( - # [ - # anatomy["project"]["code"], - # anatomy["parent"], - # anatomy["asset"], - # anatomy["task"]["name"], - # "v{:03}".format(int(anatomy["version"])), - # ] - # ) - # Initial editorial Shotgrid versions don't need task in name - if anatomy["app"] == "hiero": - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - else: - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - ### Ends Alkemy-X Override ### + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) version = self._find_existing_version(code, context) @@ -64,9 +42,9 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - # Get representation path from published_path or create it from stagingDir if not existent - local_path = get_representation_path(instance, representation, False) - self.log.info("Local path: %s", local_path) + local_path = get_representation_path( + instance, representation, False + ) if "shotgridreview" in representation.get("tags", []): diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index ac918381c0..d486b2179a 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -168,7 +168,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = get_representation_path(instance, repre, False) + repre_thumbnail_path = get_representation_path( + instance, repre, False + ) if os.path.exists(repre_thumbnail_path): thumbnail_path = repre_thumbnail_path break @@ -182,7 +184,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = get_representation_path(instance, repre, False) + repre_review_path = get_representation_path( + instance, repre, False + ) if os.path.exists(repre_review_path): review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists From 27150e4abb148e7dfc4eb673ea4affa9c080f55f Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 20:23:26 +0100 Subject: [PATCH 062/281] Address feedback on PR and move function to pipeline.publish.lib --- .../publish/integrate_ftrack_instances.py | 8 ++-- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 4 +- .../plugins/publish/integrate_slack_api.py | 6 +-- openpype/pipeline/publish/__init__.py | 2 + openpype/pipeline/publish/lib.py | 46 +++++++++++++++++++ openpype/plugins/publish/integrate.py | 46 ------------------- 7 files changed, 59 insertions(+), 57 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index c3baecec67..d6cb3daf0d 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -3,6 +3,7 @@ import json import copy import pyblish.api +from openpype.pipeline.publish import get_publish_repre_path from openpype.lib.openpype_version import get_openpype_version from openpype.lib.transcoding import ( get_ffprobe_streams, @@ -10,7 +11,6 @@ from openpype.lib.transcoding import ( ) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.transcoding import VIDEO_EXTENSIONS -from openpype.plugins.publish.integrate import get_representation_path class IntegrateFtrackInstance(pyblish.api.InstancePlugin): @@ -154,7 +154,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if not review_representations or has_movie_review: for repre in thumbnail_representations: - repre_path = get_representation_path(instance, repre, False) + repre_path = get_publish_repre_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -211,7 +211,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "from {}".format(repre)) continue - repre_path = get_representation_path(instance, repre, False) + repre_path = get_publish_repre_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -325,7 +325,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Add others representations as component for repre in other_representations: - published_path = get_representation_path(instance, repre, True) + published_path = get_publish_repre_path(instance, repre, True) if not published_path: continue # Create copy of base comp item and append it diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index 7789a47074..fc15d5515f 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -1,7 +1,7 @@ import os import pyblish.api -from openpype.plugins.publish.integrate import get_representation_path +from openpype.pipeline.publish import get_publish_repre_path class IntegrateShotgridPublish(pyblish.api.InstancePlugin): @@ -24,7 +24,7 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = get_representation_path( + local_path = get_publish_repre_path( instance, representation, False ) code = os.path.basename(local_path) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index 94fc4ae9e8..adfdca718c 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype.plugins.publish.integrate import get_representation_path +from openpype.pipeline.publish import get_publish_repre_path class IntegrateShotgridVersion(pyblish.api.InstancePlugin): @@ -42,7 +42,7 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - local_path = get_representation_path( + local_path = get_publish_repre_path( instance, representation, False ) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d486b2179a..4e2557ccc7 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -8,8 +8,8 @@ from abc import ABCMeta, abstractmethod import time from openpype.client import OpenPypeMongoConnection +from openpype.pipeline.publish import get_publish_repre_path from openpype.lib.plugin_tools import prepare_template_data -from openpype.plugins.publish.integrate import get_representation_path class IntegrateSlackAPI(pyblish.api.InstancePlugin): @@ -168,7 +168,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = get_representation_path( + repre_thumbnail_path = get_publish_repre_path( instance, repre, False ) if os.path.exists(repre_thumbnail_path): @@ -184,7 +184,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = get_representation_path( + repre_review_path = get_publish_repre_path( instance, repre, False ) if os.path.exists(repre_review_path): diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index dc6fc0f97a..5be973ad86 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -36,6 +36,7 @@ from .lib import ( filter_instances_for_context_plugin, context_plugin_should_run, get_instance_staging_dir, + get_publish_repre_path, ) from .abstract_expected_files import ExpectedFiles @@ -79,6 +80,7 @@ __all__ = ( "filter_instances_for_context_plugin", "context_plugin_should_run", "get_instance_staging_dir", + "get_publish_repre_path", "ExpectedFiles", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c76671fa39..e206c4552c 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -632,3 +632,49 @@ def get_instance_staging_dir(instance): instance.data["stagingDir"] = staging_dir return staging_dir + + +def get_publish_repre_path(instance, repre, only_published): + """Get representation path that can be used for integration. + + When 'only_published' is set to true the validation of path is not + relevant. In that case we just need what is set in 'published_path' + as "reference". The reference is not used to get or upload the file but + for reference where the file was published. + + Args: + instance (pyblish.Instance): Processed instance object. Used + for source of staging dir if representation does not have + filled it. + repre (dict): Representation on instance which could be and + could not be integrated with main integrator. + only_published (bool): Care only about published paths and + ignore if filepath is not existing anymore. + + Returns: + str: Path to representation file. + None: Path is not filled or does not exists. + """ + + published_path = repre.get("published_path") + if published_path: + published_path = os.path.normpath(published_path) + if os.path.exists(published_path): + return published_path + + if only_published: + return published_path + + comp_files = repre["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + staging_dir = repre.get("stagingDir") + if not staging_dir: + staging_dir = get_instance_staging_dir(instance) + src_path = os.path.normpath(os.path.join(staging_dir, filename)) + if os.path.exists(src_path): + return src_path + return None diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 854cf8b9ec..7b73943c37 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -53,52 +53,6 @@ def get_frame_padded(frame, padding): return "{frame:0{padding}d}".format(padding=padding, frame=frame) -def get_representation_path(instance, repre, only_published): - """Get representation path that can be used for integration. - - When 'only_published' is set to true the validation of path is not - relevant. In that case we just need what is set in 'published_path' - as "reference". The reference is not used to get or upload the file but - for reference where the file was published. - - Args: - instance (pyblish.Instance): Processed instance object. Used - for source of staging dir if representation does not have - filled it. - repre (dict): Representation on instance which could be and - could not be integrated with main integrator. - only_published (bool): Care only about published paths and - ignore if filepath is not existing anymore. - - Returns: - str: Path to representation file. - None: Path is not filled or does not exists. - """ - - published_path = repre.get("published_path") - if published_path: - published_path = os.path.normpath(published_path) - if os.path.exists(published_path): - return published_path - - if only_published: - return published_path - - comp_files = repre["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] - else: - filename = comp_files - - staging_dir = repre.get("stagingDir") - if not staging_dir: - staging_dir = instance.data["stagingDir"] - src_path = os.path.normpath(os.path.join(staging_dir, filename)) - if os.path.exists(src_path): - return src_path - return None - - class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. From abc4ecf59d938201478019fe1e9619a5600c9f70 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:22:22 +0800 Subject: [PATCH 063/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 3 +-- openpype/hosts/max/api/lib_rendersettings.py | 3 +-- openpype/hosts/max/plugins/publish/collect_render.py | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a7361a5a25..ddc5d8111f 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -51,8 +51,7 @@ class RenderProducts(object): renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): + renderer == "Quicksilver_Hardware_Renderer"): render_elem_list = self.render_elements_product(output_file, startFrame, endFrame, diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 90398e841c..aa523348dc 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -91,8 +91,7 @@ class RenderSettings(object): renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): + renderer == "Quicksilver_Hardware_Renderer"): self.render_element_layer(output, width, height, img_fmt) rt.rendSaveFile = True diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index dd85afd586..549c784e56 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -37,14 +37,12 @@ class CollectRender(pyblish.api.InstancePlugin): if ( renderer_name == "ART_Renderer" or renderer_name == "Default_Scanline_Renderer" or - renderer_name == "Quicksilver_Hardware_Renderer" - ): + renderer_name == "Quicksilver_Hardware_Renderer"): plugin = "3dsmax" if ( renderer_name == "V_Ray_6_Hotfix_3" or - renderer_name == "V_Ray_GPU_6_Hotfix_3" - ): + renderer_name == "V_Ray_GPU_6_Hotfix_3"): plugin = "Vray" if renderer_name == "Redshift Renderer": From 81b894ed13e5dc5d0743313f814ec9639f4d516a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:24:20 +0800 Subject: [PATCH 064/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 3 +-- openpype/hosts/max/api/lib_rendersettings.py | 3 +-- openpype/hosts/max/plugins/publish/collect_render.py | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index ddc5d8111f..9becd2b5e5 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -45,8 +45,7 @@ class RenderProducts(object): if renderer == "VUE_File_Renderer": return full_render_list - if ( - renderer == "ART_Renderer" or + if (renderer == "ART_Renderer" or renderer == "Redshift Renderer" or renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index aa523348dc..176c797405 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -85,8 +85,7 @@ class RenderSettings(object): if renderer == "Arnold": return - if ( - renderer == "ART_Renderer" or + if (renderer == "ART_Renderer" or renderer == "Redshift Renderer" or renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 549c784e56..cd991b36eb 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -34,14 +34,12 @@ class CollectRender(pyblish.api.InstancePlugin): renderer_class = get_current_renderer() renderer_name = str(renderer_class).split(":")[0] # setup the plugin as 3dsmax for the internal renderer - if ( - renderer_name == "ART_Renderer" or + if (renderer_name == "ART_Renderer" or renderer_name == "Default_Scanline_Renderer" or renderer_name == "Quicksilver_Hardware_Renderer"): plugin = "3dsmax" - if ( - renderer_name == "V_Ray_6_Hotfix_3" or + if (renderer_name == "V_Ray_6_Hotfix_3" or renderer_name == "V_Ray_GPU_6_Hotfix_3"): plugin = "Vray" From 8f016413d1075d027b863765e0a4595393b48ae8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:26:30 +0800 Subject: [PATCH 065/281] hound fix --- openpype/hosts/max/plugins/publish/collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index cd991b36eb..f5d99af63e 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -34,9 +34,9 @@ class CollectRender(pyblish.api.InstancePlugin): renderer_class = get_current_renderer() renderer_name = str(renderer_class).split(":")[0] # setup the plugin as 3dsmax for the internal renderer - if (renderer_name == "ART_Renderer" or - renderer_name == "Default_Scanline_Renderer" or - renderer_name == "Quicksilver_Hardware_Renderer"): + if (renderer_name == "ART_Renderer" + or renderer_name == "Default_Scanline_Renderer" + or renderer_name == "Quicksilver_Hardware_Renderer"): plugin = "3dsmax" if (renderer_name == "V_Ray_6_Hotfix_3" or From 98d05db60b1ec4fbfa4e870bf471e5f3b4844063 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:29:26 +0800 Subject: [PATCH 066/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 12 ++++++------ openpype/hosts/max/api/lib_rendersettings.py | 12 ++++++------ openpype/hosts/max/plugins/publish/collect_render.py | 4 ++-- .../plugins/publish/submit_3dmax_deadline.py | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 9becd2b5e5..fbd1f3d50e 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -45,12 +45,12 @@ class RenderProducts(object): if renderer == "VUE_File_Renderer": return full_render_list - if (renderer == "ART_Renderer" or - renderer == "Redshift Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer"): + if (renderer == "ART_Renderer" + or renderer == "Redshift Renderer" + or renderer == "V_Ray_6_Hotfix_3" + or renderer == "V_Ray_GPU_6_Hotfix_3" + or renderer == "Default_Scanline_Renderer" + or renderer == "Quicksilver_Hardware_Renderer"): render_elem_list = self.render_elements_product(output_file, startFrame, endFrame, diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 176c797405..c1e376746a 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -85,12 +85,12 @@ class RenderSettings(object): if renderer == "Arnold": return - if (renderer == "ART_Renderer" or - renderer == "Redshift Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer"): + if (renderer == "ART_Renderer" + or renderer == "Redshift Renderer" + or renderer == "V_Ray_6_Hotfix_3" + or renderer == "V_Ray_GPU_6_Hotfix_3" + or renderer == "Default_Scanline_Renderer" + or renderer == "Quicksilver_Hardware_Renderer"): self.render_element_layer(output, width, height, img_fmt) rt.rendSaveFile = True diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index f5d99af63e..c4b8ac4985 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -39,8 +39,8 @@ class CollectRender(pyblish.api.InstancePlugin): or renderer_name == "Quicksilver_Hardware_Renderer"): plugin = "3dsmax" - if (renderer_name == "V_Ray_6_Hotfix_3" or - renderer_name == "V_Ray_GPU_6_Hotfix_3"): + if (renderer_name == "V_Ray_6_Hotfix_3" + or renderer_name == "V_Ray_GPU_6_Hotfix_3"): plugin = "Vray" if renderer_name == "Redshift Renderer": diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index faeb071524..88834e4a91 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -126,7 +126,7 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload, verify=False) + response = requests.post(url, json=payload) if not response.ok: raise Exception(response.text) # Store output dir for unified publisher (filesequence) From fe7b6fbd315fcc7b13803ee7944ce37f46c9051c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:43:53 +0800 Subject: [PATCH 067/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 14 ++++++++------ openpype/hosts/max/api/lib_rendersettings.py | 14 ++++++++------ .../hosts/max/plugins/publish/collect_render.py | 14 +++++++++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index fbd1f3d50e..4ba92f06bd 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -45,12 +45,14 @@ class RenderProducts(object): if renderer == "VUE_File_Renderer": return full_render_list - if (renderer == "ART_Renderer" - or renderer == "Redshift Renderer" - or renderer == "V_Ray_6_Hotfix_3" - or renderer == "V_Ray_GPU_6_Hotfix_3" - or renderer == "Default_Scanline_Renderer" - or renderer == "Quicksilver_Hardware_Renderer"): + if ( + renderer == "ART_Renderer" or + renderer == "Redshift Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer"\ + ): render_elem_list = self.render_elements_product(output_file, startFrame, endFrame, diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index c1e376746a..ef8ad6bdc5 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -85,12 +85,14 @@ class RenderSettings(object): if renderer == "Arnold": return - if (renderer == "ART_Renderer" - or renderer == "Redshift Renderer" - or renderer == "V_Ray_6_Hotfix_3" - or renderer == "V_Ray_GPU_6_Hotfix_3" - or renderer == "Default_Scanline_Renderer" - or renderer == "Quicksilver_Hardware_Renderer"): + if ( + renderer == "ART_Renderer" or + renderer == "Redshift Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer" + ): self.render_element_layer(output, width, height, img_fmt) rt.rendSaveFile = True diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index c4b8ac4985..59a1450691 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -34,13 +34,17 @@ class CollectRender(pyblish.api.InstancePlugin): renderer_class = get_current_renderer() renderer_name = str(renderer_class).split(":")[0] # setup the plugin as 3dsmax for the internal renderer - if (renderer_name == "ART_Renderer" - or renderer_name == "Default_Scanline_Renderer" - or renderer_name == "Quicksilver_Hardware_Renderer"): + if ( + renderer_name == "ART_Renderer" or + renderer_name == "Default_Scanline_Renderer" or + renderer_name == "Quicksilver_Hardware_Renderer" + ): plugin = "3dsmax" - if (renderer_name == "V_Ray_6_Hotfix_3" - or renderer_name == "V_Ray_GPU_6_Hotfix_3"): + if ( + renderer_name == "V_Ray_6_Hotfix_3" or + renderer_name == "V_Ray_GPU_6_Hotfix_3" + ): plugin = "Vray" if renderer_name == "Redshift Renderer": From 07e14442b172a23a7c019be10a135333526d9ee2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 16:44:41 +0800 Subject: [PATCH 068/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 4ba92f06bd..44efed2c36 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -51,7 +51,7 @@ class RenderProducts(object): renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer"\ + renderer == "Quicksilver_Hardware_Renderer" ): render_elem_list = self.render_elements_product(output_file, startFrame, From c54ead7ad2b2110d7a4786e986002dd8de270cf5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:37:37 +0800 Subject: [PATCH 069/281] add arnold camera and add 3dmax as renderer --- openpype/hosts/max/api/lib_rendersettings.py | 14 ++++++++++++- .../max/plugins/publish/collect_render.py | 21 +------------------ .../projects_schema/schema_project_max.json | 1 - 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index ef8ad6bdc5..db4e53720e 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -83,7 +83,7 @@ class RenderSettings(object): return # TODO: Finish the arnold render setup if renderer == "Arnold": - return + self.arnold_setup() if ( renderer == "ART_Renderer" or @@ -97,6 +97,18 @@ class RenderSettings(object): rt.rendSaveFile = True + def arnold_setup(self): + # get Arnold RenderView run in the background + # for setting up renderable camera + arv = rt.MAXToAOps.ArnoldRenderView() + render_camera = rt.viewport.GetCamera() + arv.setOption("Camera", str(render_camera)) + + aovmgr = rt.renderers.current.AOVManager + aovmgr.drivers = "#()" + + arv.close() + def render_element_layer(self, dir, width, height, ext): """For Renderers with render elements""" rt.renderWidth = width diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 59a1450691..ce8d62e089 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -34,25 +34,6 @@ class CollectRender(pyblish.api.InstancePlugin): renderer_class = get_current_renderer() renderer_name = str(renderer_class).split(":")[0] # setup the plugin as 3dsmax for the internal renderer - if ( - renderer_name == "ART_Renderer" or - renderer_name == "Default_Scanline_Renderer" or - renderer_name == "Quicksilver_Hardware_Renderer" - ): - plugin = "3dsmax" - - if ( - renderer_name == "V_Ray_6_Hotfix_3" or - renderer_name == "V_Ray_GPU_6_Hotfix_3" - ): - plugin = "Vray" - - if renderer_name == "Redshift Renderer": - plugin = "redshift" - - if renderer_name == "Arnold": - plugin = "arnold" - data = { "subset": instance.name, "asset": asset, @@ -62,7 +43,7 @@ class CollectRender(pyblish.api.InstancePlugin): "families": ['maxrender'], "source": filepath, "files": render_layer_files, - "plugin": plugin, + "plugin": "3dsmax", "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 3d4cd5c54a..fbd9358c74 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -35,7 +35,6 @@ "multiselection": false, "defaults": "exr", "enum_items": [ - {"avi": "avi"}, {"bmp": "bmp"}, {"exr": "exr"}, {"tif": "tif"}, From 819148cfc059fb852b28c96d721dffbbf25dc995 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:38:42 +0800 Subject: [PATCH 070/281] remove unused variable --- openpype/hosts/max/plugins/publish/collect_render.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index ce8d62e089..d8b0312d43 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -31,8 +31,6 @@ class CollectRender(pyblish.api.InstancePlugin): folder = folder.replace("\\", "/") imgFormat = RenderProducts().image_format() - renderer_class = get_current_renderer() - renderer_name = str(renderer_class).split(":")[0] # setup the plugin as 3dsmax for the internal renderer data = { "subset": instance.name, From 1b1f92ba5a74c5c2dac1c941df33ae61903ec3f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:39:42 +0800 Subject: [PATCH 071/281] remove unused variable --- openpype/hosts/max/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index d8b0312d43..857ac88ea6 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -5,7 +5,6 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import legacy_io -from openpype.hosts.max.api.lib import get_current_renderer from openpype.hosts.max.api.lib_renderproducts import RenderProducts From e1aff812525d8d2bd4aa0308e68db68994ade388 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:56:37 +0800 Subject: [PATCH 072/281] correct separator issue in naming convention --- openpype/hosts/max/api/lib_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index db4e53720e..8f7e91822c 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -75,7 +75,7 @@ class RenderSettings(object): )] except KeyError: aov_separator = "." - outputFilename = "{0}.{1}".format(output, img_fmt) + outputFilename = "{0}..{1}".format(output, img_fmt) outputFilename = outputFilename.replace("{aov_separator}", aov_separator) rt.rendOutputFilename = outputFilename @@ -122,7 +122,7 @@ class RenderSettings(object): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") render_element = os.path.join(dir, renderpass) - aov_name = "{0}.{1}".format(render_element, ext) + aov_name = "{0}..{1}".format(render_element, ext) try: aov_separator = self._aov_chars[( self._project_settings["maya"] From 31424672aa22d08629e87f49dfadcdd2a8ba3e19 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:59:35 +0800 Subject: [PATCH 073/281] correct separator issue in naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 44efed2c36..fd0eb947af 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -92,9 +92,10 @@ class RenderProducts(object): render_dir = os.path.join(folder, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}.{1}.{2}".format(render_dir, - str(f), - fmt) + render_element = "{0}_{1}..{2}.{2}".format(folder, + renderpass, + str(f), + fmt) render_element = render_element.replace("\\", "/") render_dirname.append(render_element) From 536cbd2913d89b64acc8b41b58bd77efeefb51c4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 18:59:58 +0800 Subject: [PATCH 074/281] correct separator issue in naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index fd0eb947af..83b5a0bc35 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -89,7 +89,6 @@ class RenderProducts(object): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_dir = os.path.join(folder, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): render_element = "{0}_{1}..{2}.{2}".format(folder, From 4bd05046236759e6209a256af639ee943e091406 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 19:00:23 +0800 Subject: [PATCH 075/281] correct separator issue in naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 83b5a0bc35..a924505d7e 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -91,7 +91,7 @@ class RenderProducts(object): if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}_{1}..{2}.{2}".format(folder, + render_element = "{0}_{1}..{2}.{3}".format(folder, renderpass, str(f), fmt) From 15d7a7589fa119010510232e3ae243f0e6834383 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:04:06 +0100 Subject: [PATCH 076/281] fix context collection from create context --- .../publish/collect_from_create_context.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index d3398c885e..5fcf8feb56 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -32,7 +32,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): thumbnail_paths_by_instance_id.get(None) ) - project_name = create_context.project_name + project_name = create_context.get_current_project_name() if project_name: context.data["projectName"] = project_name @@ -53,11 +53,15 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): context.data.update(create_context.context_data_to_store()) context.data["newPublishing"] = True # Update context data - for key in ("AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK"): - value = create_context.dbcon.Session.get(key) - if value is not None: - legacy_io.Session[key] = value - os.environ[key] = value + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() + for key, value in ( + ("AVALON_PROJECT", project_name), + ("AVALON_ASSET", asset_name), + ("AVALON_TASK", task_name) + ): + legacy_io.Session[key] = value + os.environ[key] = value def create_instance( self, From 0a7d7e45638987a53ac35c2dd9c69d38cfe9855e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 19:04:28 +0800 Subject: [PATCH 077/281] correct separator issue in naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- openpype/hosts/max/api/lib_rendersettings.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a924505d7e..4c9c9b8088 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -91,7 +91,7 @@ class RenderProducts(object): if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}_{1}..{2}.{3}".format(folder, + render_element = "{0}_{1}.{2}.{3}".format(folder, renderpass, str(f), fmt) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 8f7e91822c..30a252e07a 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -121,8 +121,7 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_element = os.path.join(dir, renderpass) - aov_name = "{0}..{1}".format(render_element, ext) + aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) try: aov_separator = self._aov_chars[( self._project_settings["maya"] From a3bc0e6debfe0e0e1dabff1bba543b30af738e65 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 19:54:14 +0800 Subject: [PATCH 078/281] update aov naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 9 ++++----- openpype/hosts/max/api/lib_rendersettings.py | 15 +++------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 4c9c9b8088..84cb0c1744 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -88,13 +88,12 @@ class RenderProducts(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - + render_element = os.path.join(dir, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}_{1}.{2}.{3}".format(folder, - renderpass, - str(f), - fmt) + render_element = "{0}.{1}.{2}".format(render_element, + str(f), + fmt) render_element = render_element.replace("\\", "/") render_dirname.append(render_element) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 30a252e07a..1188d77e29 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -121,16 +121,7 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) - try: - aov_separator = self._aov_chars[( - self._project_settings["maya"] - ["RenderSettings"] - ["aov_separator"] - )] - except KeyError: - aov_separator = "." - - aov_name = aov_name.replace("{aov_separator}", - aov_separator) + render_element = os.path.join(dir, renderpass) + dir = dir.replace(".", " ") + aov_name = "{0}..{1}".format(render_element, ext) render_elem.SetRenderElementFileName(i, aov_name) From 189a842660436b23f67de81a1548683ca3b066f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 19:55:22 +0800 Subject: [PATCH 079/281] update aov naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 84cb0c1744..912c0c89d7 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -88,12 +88,12 @@ class RenderProducts(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_element = os.path.join(dir, renderpass) + render_name = os.path.join(dir, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}.{1}.{2}".format(render_element, - str(f), - fmt) + render_element = "{0}.{1}.{2}".format(render_name, + str(f), + fmt) render_element = render_element.replace("\\", "/") render_dirname.append(render_element) From b148dec04843137622960b50c484d94ad9b0e82b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:58:17 +0100 Subject: [PATCH 080/281] Added helper method to return unified information on create error --- openpype/pipeline/create/context.py | 70 +++++++++++++++++------------ 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 190d542724..dfe60d438b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1538,6 +1538,44 @@ class CreateContext: pre_create_data ) + def _create_with_unified_error( + self, identifier, creator, *args, **kwargs + ): + error_message = "Failed to run Creator with identifier \"{}\". {}" + + label = None + add_traceback = False + result = None + fail_info = None + success = False + + try: + # Try to get creator and his label + if creator is None: + creator = self._get_creator_in_create(identifier) + label = getattr(creator, "label", label) + + # Run create + result = creator.create(*args, **kwargs) + success = True + + except CreatorError: + exc_info = sys.exc_info() + self.log.warning(error_message.format(identifier, exc_info[1])) + + except: + add_traceback = True + exc_info = sys.exc_info() + self.log.warning( + error_message.format(identifier, ""), + exc_info=True + ) + + if not success: + fail_info = prepare_failed_creator_operation_info( + identifier, label, exc_info, add_traceback + ) + return result, fail_info def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. @@ -1663,37 +1701,11 @@ class CreateContext: Reset instances if any autocreator executed properly. """ - error_message = "Failed to run AutoCreator with identifier \"{}\". {}" failed_info = [] for identifier, creator in self.autocreators.items(): - label = creator.label - failed = False - add_traceback = False - try: - creator.create() - - except CreatorError: - failed = True - exc_info = sys.exc_info() - self.log.warning(error_message.format(identifier, exc_info[1])) - - # Use bare except because some hosts raise their exceptions that - # do not inherit from python's `BaseException` - except: - failed = True - add_traceback = True - exc_info = sys.exc_info() - self.log.warning( - error_message.format(identifier, ""), - exc_info=True - ) - - if failed: - failed_info.append( - prepare_failed_creator_operation_info( - identifier, label, exc_info, add_traceback - ) - ) + _, fail_info = self._create_with_unified_error(identifier, creator) + if fail_info is not None: + failed_info.append(fail_info) if failed_info: raise CreatorsCreateFailed(failed_info) From fac10d26337157bc253a1af90ae0a1040ff49533 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:58:51 +0100 Subject: [PATCH 081/281] added public method 'create_with_unified_error' used in publisher --- openpype/pipeline/create/context.py | 25 +++++++++++++++++++++++++ openpype/tools/publisher/control.py | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index dfe60d438b..2a92d21225 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1576,6 +1576,31 @@ class CreateContext: identifier, label, exc_info, add_traceback ) return result, fail_info + + def create_with_unified_error(self, identifier, *args, **kwargs): + """Trigger create but raise only one error if anything fails. + + Added to raise unified exception. Capture any possible issues and + reraise it with unified information. + + Args: + identifier (str): Identifier of creator. + *args (Tuple[Any]): Arguments for create method. + **kwargs (Dict[Any, Any]): Keyword argument for create method. + + Raises: + CreatorsCreateFailed: When creation fails due to any possible + reason. If anything goes wrong this is only possible exception + the method should raise. + """ + + result, fail_info = self._create_with_unified_error( + identifier, None, *args, **kwargs + ) + if fail_info is not None: + raise CreatorsCreateFailed([fail_info]) + return result + def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 670c22a43e..11215b5ff8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2017,9 +2017,10 @@ class PublisherController(BasePublisherController): success = True try: - self._create_context.raw_create( + self._create_context.create_with_unified_error( creator_identifier, subset_name, instance_data, options ) + except CreatorsOperationFailed as exc: success = False self._emit_event( From cb84cf769eea9d5a018ef67dd6e4cb0d6d7276b8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 13:00:28 +0100 Subject: [PATCH 082/281] 'create' method is not triggering 'raw_create' --- openpype/pipeline/create/context.py | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2a92d21225..3287141970 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1415,6 +1415,30 @@ class CreateContext: with self.bulk_instances_collection(): self._bulk_instances_to_process.append(instance) + def _get_creator_in_create(self, identifier): + """Creator by identifier with unified error. + + Helper method to get creator by identifier with same error when creator + is not available. + + Args: + identifier (str): Identifier of creator plugin. + + Returns: + BaseCreator: Creator found by identifier. + + Raises: + CreatorError: When identifier is not known. + """ + + creator = self.creators.get(identifier) + # Fake CreatorError (Could be maybe specific exception?) + if creator is None: + raise CreatorError( + "Creator {} was not found".format(identifier) + ) + return creator + def raw_create(self, identifier, *args, **kwargs): """Wrapper for creators to trigger 'create' method. @@ -1497,14 +1521,9 @@ class CreateContext: Raises: CreatorError: If creator was not found or asset is empty. - CreatorsCreateFailed: When creation fails. """ - creator = self.creators.get(creator_identifier) - if creator is None: - raise CreatorError( - "Creator {} was not found".format(creator_identifier) - ) + creator = self._get_creator_in_create(creator_identifier) project_name = self.project_name if asset_doc is None: @@ -1531,8 +1550,7 @@ class CreateContext: "task": task_name, "variant": variant } - return self.raw_create( - creator_identifier, + return creator.create( subset_name, instance_data, pre_create_data From 0cb78a10e6cd7b0c7307c70be1827e2e8c1d1f2e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 13:00:49 +0100 Subject: [PATCH 083/281] removed unused 'raw_create' method --- openpype/pipeline/create/context.py | 53 ----------------------------- 1 file changed, 53 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 3287141970..078c50acc2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1439,59 +1439,6 @@ class CreateContext: ) return creator - def raw_create(self, identifier, *args, **kwargs): - """Wrapper for creators to trigger 'create' method. - - Different types of creators may expect different arguments thus the - hints for args are blind. - - Args: - identifier (str): Creator's identifier. - *args (Tuple[Any]): Arguments for create method. - **kwargs (Dict[Any, Any]): Keyword argument for create method. - - Raises: - CreatorsCreateFailed: When creation fails. - """ - - error_message = "Failed to run Creator with identifier \"{}\". {}" - creator = self.creators.get(identifier) - label = getattr(creator, "label", None) - failed = False - add_traceback = False - exc_info = None - result = None - try: - # Fake CreatorError (Could be maybe specific exception?) - if creator is None: - raise CreatorError( - "Creator {} was not found".format(identifier) - ) - - result = creator.create(*args, **kwargs) - - except CreatorError: - failed = True - exc_info = sys.exc_info() - self.log.warning(error_message.format(identifier, exc_info[1])) - - except: - failed = True - add_traceback = True - exc_info = sys.exc_info() - self.log.warning( - error_message.format(identifier, ""), - exc_info=True - ) - - if failed: - raise CreatorsCreateFailed([ - prepare_failed_creator_operation_info( - identifier, label, exc_info, add_traceback - ) - ]) - return result - def create( self, creator_identifier, From 0d2b6da3ef7e012d00b938a9b47609e66bb928e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 20:02:28 +0800 Subject: [PATCH 084/281] update aov naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 912c0c89d7..5f96b13273 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -88,7 +88,7 @@ class RenderProducts(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_name = os.path.join(dir, renderpass) + render_name = os.path.join(folder, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): render_element = "{0}.{1}.{2}".format(render_name, From be573252486287a04dc37a5d0daa6edb13fb8ce5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 20:22:19 +0800 Subject: [PATCH 085/281] update aov naming convention --- openpype/hosts/max/api/lib_renderproducts.py | 8 ++++---- openpype/hosts/max/api/lib_rendersettings.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 5f96b13273..e3cccff982 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -88,12 +88,12 @@ class RenderProducts(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_name = os.path.join(folder, renderpass) if renderlayer_name.enabled: for f in range(startFrame, endFrame): - render_element = "{0}.{1}.{2}".format(render_name, - str(f), - fmt) + render_element = "{0}_{1}.{2}.{3}".format(folder, + renderpass, + str(f), + fmt) render_element = render_element.replace("\\", "/") render_dirname.append(render_element) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 1188d77e29..6d2aa678b7 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -122,6 +122,5 @@ class RenderSettings(object): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") render_element = os.path.join(dir, renderpass) - dir = dir.replace(".", " ") aov_name = "{0}..{1}".format(render_element, ext) render_elem.SetRenderElementFileName(i, aov_name) From 9ba3d144f381d62127c90542d280ad80c0306b18 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 Feb 2023 20:24:03 +0800 Subject: [PATCH 086/281] update aov naming convention --- openpype/hosts/max/api/lib_rendersettings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 6d2aa678b7..2324d743eb 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -121,6 +121,5 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - render_element = os.path.join(dir, renderpass) - aov_name = "{0}..{1}".format(render_element, ext) + aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) render_elem.SetRenderElementFileName(i, aov_name) From a3c9f792c81ac6276594f73d6dc5152bce0b12cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 14:29:20 +0100 Subject: [PATCH 087/281] refactor tempdir creator function wip --- openpype/pipeline/publish/lib.py | 67 ++++++-------------------------- openpype/pipeline/tempdir.py | 62 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 openpype/pipeline/tempdir.py diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index b3d273781e..380f0df91a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -12,13 +12,15 @@ import pyblish.api from openpype.lib import ( Logger, - filter_profiles, - StringTemplate + filter_profiles ) from openpype.settings import ( get_project_settings, get_system_settings, ) +from openpype.pipeline import ( + tempdir +) from .contants import ( DEFAULT_PUBLISH_TEMPLATE, @@ -645,24 +647,12 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - openpype_temp_dir = os.getenv("OPENPYPE_TMPDIR") - custom_temp_dir = None - if openpype_temp_dir: - if "{" in openpype_temp_dir: - # path is anatomy template - custom_temp_dir = _format_staging_dir( - instance, openpype_temp_dir - ) - else: - # path is absolute - custom_temp_dir = openpype_temp_dir - - if not os.path.exists(custom_temp_dir): - try: - # create it if it doesnt exists - os.makedirs(custom_temp_dir) - except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) + anatomy_data = instance.data.get("anatomy_data") + project_name = + # get customized tempdir path from `OPENPYPE_TEMPDIR` env var + custom_temp_dir = tempdir.create_custom_tempdir( + instance.data["anatomy_data"]["project"]["name"] + ) if custom_temp_dir: staging_dir = os.path.normpath( @@ -677,39 +667,4 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - return staging_dir - - -def _format_staging_dir(instance, openpype_temp_dir): - """ Formating template - - Template path formatting is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - - Args: - instance (pyblish.Instance): instance object - openpype_temp_dir (str): path string - - Returns: - str: formated path - """ - anatomy = instance.context.data["anatomy"] - # get anatomy formating data - # so template formating is supported - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - anatomy_data["root"] = anatomy.roots - - result = StringTemplate.format_template( - openpype_temp_dir, anatomy_data).normalized() - - # create the dir in case it doesnt exists - os.makedirs(os.path.dirname(result)) - return result + return staging_dir \ No newline at end of file diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py new file mode 100644 index 0000000000..c73fce2e9a --- /dev/null +++ b/openpype/pipeline/tempdir.py @@ -0,0 +1,62 @@ +import os + +from openpype.lib import ( + Anatomy, + StringTemplate +) + +def create_custom_tempdir(project_name, anatomy=None, formating_data=None): + """ Create custom tempdir + + Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + + Args: + instance (pyblish.Instance): instance object + openpype_temp_dir (str): path string + + Returns: + str: formated path + """ + openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") + if not openpype_tempdir: + return + + custom_tempdir = None + if "{" in openpype_tempdir: + if anatomy is None: + anatomy = Anatomy(project_name) + # create base formate data + data = { + "root": anatomy.roots + } + if formating_data is None: + # We still don't have `project_code` on Anatomy... + project_doc = anatomy.get_project_doc_from_cache(project_name) + data["project"] = { + "name": project_name, + "code": project_doc["data"]["code"], + } + else: + data["project"] = formating_data["project"] + + # path is anatomy template + custom_tempdir = StringTemplate.format_template( + openpype_tempdir, data).normalized() + + else: + # path is absolute + custom_tempdir = openpype_tempdir + + # create he dir path if it doesnt exists + if not os.path.exists(custom_tempdir): + try: + # create it if it doesnt exists + os.makedirs(custom_tempdir) + except IOError as error: + raise IOError("Path couldn't be created: {}".format(error)) + + return custom_tempdir From 304d7584042454193be5b1c85f0dc87e06c1d4c3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 14:40:40 +0100 Subject: [PATCH 088/281] renaming module for temporarydir, fixing docstring --- openpype/pipeline/publish/lib.py | 19 ++++++++++--------- .../pipeline/{tempdir.py => temporarydir.py} | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) rename openpype/pipeline/{tempdir.py => temporarydir.py} (82%) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 380f0df91a..c6c8b71b24 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -19,7 +19,7 @@ from openpype.settings import ( get_system_settings, ) from openpype.pipeline import ( - tempdir + temporarydir ) from .contants import ( @@ -626,11 +626,6 @@ def get_instance_staging_dir(instance): Available anatomy formatting keys: - root[work | ] - project[name | code] - - asset - - hierarchy - - task - - username - - app Note: Staging dir does not have to be necessarily in tempdir so be carefull @@ -648,10 +643,16 @@ def get_instance_staging_dir(instance): return staging_dir anatomy_data = instance.data.get("anatomy_data") - project_name = + anatomy = instance.data.get("anatomy") + + if anatomy_data: + project_name = anatomy_data["project"]["name"] + else: + project_name = os.getenv("AVALON_PROJECT") + # get customized tempdir path from `OPENPYPE_TEMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir( - instance.data["anatomy_data"]["project"]["name"] + custom_temp_dir = temporarydir.create_custom_tempdir( + project_name, anatomy=anatomy, formating_data=anatomy_data ) if custom_temp_dir: diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/temporarydir.py similarity index 82% rename from openpype/pipeline/tempdir.py rename to openpype/pipeline/temporarydir.py index c73fce2e9a..31586d82c8 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/temporarydir.py @@ -1,9 +1,11 @@ -import os +""" +Temporary folder operations +""" + +import os +from openpype.lib import StringTemplate +from openpype.pipeline import Anatomy -from openpype.lib import ( - Anatomy, - StringTemplate -) def create_custom_tempdir(project_name, anatomy=None, formating_data=None): """ Create custom tempdir @@ -15,11 +17,12 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): - project[name | code] Args: - instance (pyblish.Instance): instance object - openpype_temp_dir (str): path string + project_name (str): name of project + anatomy (openpype.pipeline.Anatomy): Anatomy object + formating_data (dict): formating data used for filling template. Returns: - str: formated path + bool | str: formated path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 171b9bc3dbc079d08248cc2cafa40342b9bcd762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Thu, 9 Feb 2023 05:49:02 -0800 Subject: [PATCH 089/281] Make only_published argument optional Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index e206c4552c..92c43c99e8 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -634,7 +634,7 @@ def get_instance_staging_dir(instance): return staging_dir -def get_publish_repre_path(instance, repre, only_published): +def get_publish_repre_path(instance, repre, only_published=False): """Get representation path that can be used for integration. When 'only_published' is set to true the validation of path is not From 5de967b6447fb2b7f7dc84ad84704857afa35af8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 15:07:40 +0100 Subject: [PATCH 090/281] updated documentation --- openpype/pipeline/publish/lib.py | 2 +- website/docs/admin_environment.md | 30 +++++++++++++++++++++++++++ website/docs/admin_settings_system.md | 29 ++++---------------------- website/sidebars.js | 1 + 4 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 website/docs/admin_environment.md diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c6c8b71b24..aaa2dd444a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -668,4 +668,4 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - return staging_dir \ No newline at end of file + return staging_dir diff --git a/website/docs/admin_environment.md b/website/docs/admin_environment.md new file mode 100644 index 0000000000..2cc558b530 --- /dev/null +++ b/website/docs/admin_environment.md @@ -0,0 +1,30 @@ +--- +id: admin_environment +title: Environment +sidebar_label: Environment +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## OPENPYPE_TMPDIR: + - Custom staging dir directory + - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` + - supported formating keys: + - root[work] + - project[name | code] + +## OPENPYPE_DEBUG + - setting logger to debug mode + - example value: "1" (to activate) + +## OPENPYPE_LOG_LEVEL + - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) + - example value: "10" + +## OPENPYPE_MONGO +- If set it takes precedence over the one set in keyring +- for more details on how to use it go [here](admin_use#check-for-mongodb-database-connection) + +## OPENPYPE_USERNAME +- if set it overides system created username diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 39b58e6f81..6a17844755 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -14,39 +14,18 @@ Settings applicable to the full studio. ![general_settings](assets/settings/settings_system_general.png) ### Studio Name - - Full name of the studio (can be used as variable on some places) +Full name of the studio (can be used as variable on some places) ### Studio Code - - Studio acronym or a short code (can be used as variable on some places) +Studio acronym or a short code (can be used as variable on some places) ### Admin Password - - After setting admin password, normal user won't have access to OpenPype settings +After setting admin password, normal user won't have access to OpenPype settings and Project Manager GUI. Please keep in mind that this is a studio wide password and it is meant purely as a simple barrier to prevent artists from accidental setting changes. ### Environment - - Globally applied environment variables that will be appended to any OpenPype process in the studio. - - OpenPype is using some keys to configure some tools. Here are some: - -#### OPENPYPE_TMPDIR: - - Custom staging dir directory - - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` - - supported formating keys: - - root[work] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - -#### OPENPYPE_DEBUG - - setting logger to debug mode - - example value: "1" (to activate) - -#### OPENPYPE_LOG_LEVEL - - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) - - example value: "10" +Globally applied environment variables that will be appended to any OpenPype process in the studio. ### Disk mapping - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. diff --git a/website/sidebars.js b/website/sidebars.js index cc945a019e..ed4ff45db8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -85,6 +85,7 @@ module.exports = { type: "category", label: "Configuration", items: [ + "admin_environment", "admin_settings", "admin_settings_system", "admin_settings_project_anatomy", From d774eab62603a8356ed55b6c74255332b6c675ee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 15:08:18 +0100 Subject: [PATCH 091/281] end line added --- website/docs/admin_settings_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 6a17844755..c39cac61f5 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -176,4 +176,4 @@ In the image before you can see that we set most of the environment variables in In this example MTOA will automatically will the `MAYA_VERSION`(which is set by Maya Application environment) and `MTOA_VERSION` into the `MTOA` variable. We then use the `MTOA` to set all the other variables needed for it to function within Maya. ![tools](assets/settings/tools_01.png) -All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. \ No newline at end of file +All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. From d516de4ecdfcf17b2d8f91d91535d22ef7c6ad13 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Feb 2023 16:28:55 +0000 Subject: [PATCH 092/281] Ensure content and proxy hierarchy is the same. --- .../publish/collect_arnold_scene_source.py | 4 +- .../publish/extract_arnold_scene_source.py | 51 ++++++--- .../publish/validate_arnold_scene_source.py | 106 ++++++++++++++++++ 3 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index c0275eef7b..0415808b7a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -21,10 +21,10 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if objset.endswith("content_SET"): - instance.data["setMembers"] = members + instance.data["setMembers"] = cmds.ls(members, long=True) self.log.debug("content members: {}".format(members)) elif objset.endswith("proxy_SET"): - instance.data["proxy"] = members + instance.data["proxy"] = cmds.ls(members, long=True) self.log.debug("proxy members: {}".format(members)) # Use camera in object set if present else default to render globals diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 4cff9d0183..10943dd810 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -5,14 +5,16 @@ from maya import cmds import arnold from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.hosts.maya.api.lib import ( + maintained_selection, attribute_values, delete_after +) from openpype.lib import StringTemplate class ExtractArnoldSceneSource(publish.Extractor): """Extract the content of the instance to an Arnold Scene Source file.""" - label = "Arnold Scene Source" + label = "Extract Arnold Scene Source" hosts = ["maya"] families = ["ass"] asciiAss = False @@ -124,22 +126,43 @@ class ExtractArnoldSceneSource(publish.Extractor): def _extract(self, nodes, attribute_data, kwargs): self.log.info("Writing: " + kwargs["filename"]) filenames = [] - with attribute_values(attribute_data): - with maintained_selection(): - self.log.info( - "Writing: {}".format(nodes) + # Duplicating nodes so they are direct children of the world. This + # makes the hierarchy of any exported ass file the same. + with delete_after() as delete_bin: + duplicate_nodes = [] + for node in nodes: + duplicate_transform = cmds.duplicate(node)[0] + delete_bin.append(duplicate_transform) + + # Discard the children. + shapes = cmds.listRelatives(duplicate_transform, shapes=True) + children = cmds.listRelatives( + duplicate_transform, children=True ) - cmds.select(nodes, noExpand=True) + cmds.delete(set(children) - set(shapes)) - self.log.info( - "Extracting ass sequence with: {}".format(kwargs) - ) + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] - exported_files = cmds.arnoldExportAss(**kwargs) + duplicate_nodes.append(duplicate_transform) - for file in exported_files: - filenames.append(os.path.split(file)[1]) + with attribute_values(attribute_data): + with maintained_selection(): + self.log.info( + "Writing: {}".format(duplicate_nodes) + ) + cmds.select(duplicate_nodes, noExpand=True) - self.log.info("Exported: {}".format(filenames)) + self.log.info( + "Extracting ass sequence with: {}".format(kwargs) + ) + + exported_files = cmds.arnoldExportAss(**kwargs) + + for file in exported_files: + filenames.append(os.path.split(file)[1]) + + self.log.info("Exported: {}".format(filenames)) return filenames diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py new file mode 100644 index 0000000000..ad00502d56 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -0,0 +1,106 @@ +import os +import types + +import maya.cmds as cmds +from mtoa.core import createOptions + +import pyblish.api +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishValidationError +) + + +class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): + """Validate Arnold Scene Source. + + If using proxies we need the nodes to share the same names and not be + parent to the world. This ends up needing at least two groups with content + nodes and proxy nodes in another. + """ + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["ass"] + label = "Validate Arnold Scene Source" + + def _get_nodes_data(self, nodes): + ungrouped_nodes = [] + nodes_by_name = {} + parents = [] + for node in nodes: + node_split = node.split("|") + if len(node_split) == 2: + ungrouped_nodes.append(node) + + parent = "|".join(node_split[:-1]) + if parent: + parents.append(parent) + + nodes_by_name[node_split[-1]] = node + for shape in cmds.listRelatives(node, shapes=True): + nodes_by_name[shape.split("|")[-1]] = shape + + return ungrouped_nodes, nodes_by_name, parents + + def process(self, instance): + if not instance.data["proxy"]: + return + + ungrouped_nodes = [] + + nodes, content_nodes_by_name, content_parents = self._get_nodes_data( + instance.data["setMembers"] + ) + ungrouped_nodes.extend(nodes) + + nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_data( + instance.data["proxy"] + ) + ungrouped_nodes.extend(nodes) + + # Validate against nodes directly parented to world. + if ungrouped_nodes: + raise PublishValidationError( + "Found nodes parented to the world: {}\n" + "All nodes need to be grouped.".format(ungrouped_nodes) + ) + + # Validate for content and proxy nodes amount being the same. + if len(instance.data["setMembers"]) != len(instance.data["proxy"]): + raise PublishValidationError( + "Amount of content nodes ({}) and proxy nodes ({}) needs to " + "be the same.".format( + len(instance.data["setMembers"]), + len(instance.data["proxy"]) + ) + ) + + # Validate against content and proxy nodes sharing same parent. + if list(set(content_parents) & set(proxy_parents)): + raise PublishValidationError( + "Content and proxy nodes cannot share the same parent." + ) + + # Validate for content and proxy nodes sharing same names. + sorted_content_names = sorted(content_nodes_by_name.keys()) + sorted_proxy_names = sorted(proxy_nodes_by_name.keys()) + odd_content_names = list( + set(sorted_content_names) - set(sorted_proxy_names) + ) + odd_content_nodes = [ + content_nodes_by_name[x] for x in odd_content_names + ] + odd_proxy_names = list( + set(sorted_proxy_names) - set(sorted_content_names) + ) + odd_proxy_nodes = [ + proxy_nodes_by_name[x] for x in odd_proxy_names + ] + if not sorted_content_names == sorted_proxy_names: + raise PublishValidationError( + "Content and proxy nodes need to share the same names.\n" + "Content nodes not matching: {}\n" + "Proxy nodes not matching: {}".format( + odd_content_nodes, odd_proxy_nodes + ) + ) From 8ee1a3a2d2cb7b952f6aee1385b31abee5d9add7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Feb 2023 17:11:31 +0000 Subject: [PATCH 093/281] Document proxy workflow --- website/docs/artist_hosts_maya_arnold.md | 14 ++++++++++++++ .../docs/assets/maya-arnold_scene_source.png | Bin 0 -> 15948 bytes website/docs/assets/maya-arnold_standin.png | Bin 0 -> 42985 bytes 3 files changed, 14 insertions(+) create mode 100644 website/docs/assets/maya-arnold_scene_source.png create mode 100644 website/docs/assets/maya-arnold_standin.png diff --git a/website/docs/artist_hosts_maya_arnold.md b/website/docs/artist_hosts_maya_arnold.md index b8b8da6d57..b3c02a0894 100644 --- a/website/docs/artist_hosts_maya_arnold.md +++ b/website/docs/artist_hosts_maya_arnold.md @@ -8,9 +8,23 @@ Arnold Scene Source can be published as a single file or a sequence of files, de When creating the instance, two objectsets are created; `content` and `proxy`. Meshes in the `proxy` objectset will be the viewport representation when loading as `standin`. Proxy representations are stored as `resources` of the subset. +### Arnold Scene Source Proxy Workflow +In order to utilize operators and proxies, the content and proxy nodes need to share the same names (including the shape names). This is done by parenting the content and proxy nodes into separate groups. For example: + +![Arnold Scene Source](assets/maya-arnold_scene_source.png) + ## Standin Arnold Scene Source `ass` and Alembic `abc` are supported to load as standins. +### Standin Proxy Workflow If a subset has a proxy representation, this will be used as display in the viewport. At render time the standin path will be replaced using the recommended string replacement workflow; https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_maya_operators_am_Updating_procedural_file_paths_with_string_replace_html + +Since the content and proxy nodes share the same names and hierarchy, any manually shader assignments will be shared. + + +:::note for advanced users +You can stop the proxy swapping by disabling the string replacement operator found in the container. +![Arnold Standin](assets/maya-arnold_standin.png) +::: diff --git a/website/docs/assets/maya-arnold_scene_source.png b/website/docs/assets/maya-arnold_scene_source.png new file mode 100644 index 0000000000000000000000000000000000000000..4150b78aac7bfe776d9f4b4940a56d77176a2a57 GIT binary patch literal 15948 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

M-ZZ1puA}J8G$tO$CyZX_jwrh#*yh&^Hbc( zqp^uQcI1We+v^cp-JtD)N{(l-F~a#58lGS$C(&kJ)A&>LB#bTe%GImA{8ryEF;Jyu z_!;KB`}qO6zCtd!vc`}g0*f`riZ8jn za!|yIL%;&QZS`=7>WT-^iEOC`)^FJ+g3+uV%ABUA{IX_c_~taN!5;&aYHs7KK=OS# z?#x1N`tiCq@=-&ri%(OR-{9b9w8ITggedm~be~uva`|eo1Igk$(2f^Uj(+lfHs72e z|ITn!UuTmIaw?+BuoV;E%|Z-gN?wC6bdzKQZ3+zLTfvCGj=WWZaaap}nWE;^>(LX- ztYuUGCa);z6;+EP{p}7NQ2kWz>*&}V5Uj4jBm7N2K3!}VY1(fUC>F%*cs_`8awFSL zJl+5Qj<>&@q+b$CBQ1ur|0TCv1)rwW^8iBH#?eqftjhsMlY9zB{Y$hPoPIAnd)aSg zw(vq1?gB*NI^$`6%31?OPDyvF{7WlsLwW3PW2|)6vhDpOj(c54iCetL9g^yx=;~D@ za2fS8R%Af)KhTIZAiJ#l*iNzqD0Rpc*W3#``2?W&2w+~gu>Njf?iM`$DB2`WL@S^# z#CLhR<6-uc%KP5rz|T@$QPAg3U#W6Xzk-44N;|)+wL<}mLad5AYu0`Or<^EAGm&j& zIrqh<2pJXMlBAqe8_Hp8s*ktSQ^0w- zm*Sj@mUDCRb`)rTCbpDT?vb-9nd=G0jyXA`=!IFm7tSD^C%;TJQ!AvFB#2gq~wvAskY0v5|OHK~so5JIbxysaLZXE?V@K~g;x+uQ-jZ+Q3ZQ2CdH5XkC` zTvjiz1N7uqF>Zj7{cY=U9kKs4a0A-w&!kX(Xw+}D#A55ERan^KPR=JhBo0*GB@d>R zcTyw(fn^)nVZqdI&44fU4&JY8NnSn9FP^xcu@`{O6SmY4E!Z;kJ(Jr46#DFhL9dhi z0nh%4+czu>>Mb5};GTo=XMVmNR7;PLd%>G)s6LW638+0Mfevk z{c-mXs6tR)W|pB|vBS6!Qo&@$nYE=QNOn|^^BLdT5VnbK3{>e0-y)x|aN*3=6jkeC zxz5tzCttr_QUhO6?Q|Opv5cudl$S}CA1!&?nTfpAs>&_e6_D~k5P+^7fc?Kpc8Ql6 zdqS7(p0g>6Xa5sAi!IVU7u^9U$(`%kd71UP-6@x=;@adE)`FK>ZPD-67^nx(jS%rz z&#^Wv9{)Ugt_neIezf;sbE2X{b2#@2dJ^kctH~$%Sx%*h+y2XexWjB>k84j$7`rDG zAt|QU=a-LugSF)cysd5t1vF&E?KDW?9KQd&tDAL&`_wi0z%BoL08Z*&$lnt#lD_k? zt~?@+GX?jd*0mY4@3?jf-+(LCGOcay$qL!+%%6M1TFA1yA?dL#F8NZhfXnG=m!rQM z;X|xq>XW-NXjg!w4(T@ot_B1B7Q#YJhS$k|>IKlQ#e=eHUb6)=A0^lV#v&#s;>+x7 z=Fa0Sc3_pI?+2SccF?Tk%fdWfrzkQ8s+whp}(u+3tBLDhfcCug6d z8Zu@3GS3|=HdhM)3eej$7p)z=Z7zW$s z4k6-rnQzpUIb{y@`9De*8>I-wEr&WS1t}ocp+7X3XA0qm{!9hg3M;bB!aRk3^T!H% z#Z%|12TcRk#!0J<3-myiwfzbT-6?1A#x=`iP-v=v2|P^%y(SBoy=oq%vL8o}psXaL@-@h*Gx zy&=>>C=*i-XN48J^7UH!rn2;`&_CwDU@a@?1+YWi_tD2etYV@LgmKl!Mau?q zVxJmsm5|CjA8$gRqUIi#9hv;39t7DzkF$r3@iO?5r@HauEgQS@)+$9e;{{+**E6-9 zf+ll4%@jtZhU&WWi*mVpDUWkXPF3||4;HS&Hw+^;ueXMyFKxTUNo`Vy<~*NqdjJ|U zt06IWIamNJ%AvL4`XEYRf%PhUJVf=Wap6~wk$9E1?$pGZ)2nc8WFZl#$o~nGZYs>iL+=1SBd~FHUWxOUJ9D?Sdr*ApA?nSMh(yOc0{shVNs1O1q((!Jk*UDzIt z05roA@i+_bQlQ^}bW@>3nVZWNix}REJ(l}6JdF9`HY|zb=|2*)C~>t^YuqPS#3TY0 zqzoH>2}e8s+yXY4&egjrvvFQ4{ays+gK`DC0jqR3B%R|j5@sC7+P236eK+N+cw)qm zdLlk`0qz{o_EmS5PO;wE(#OfTu(VA;OXZ75MOJL;bdjSZVV&v+$3EabJv zVJ<>Ec9JM|#Uf&R4EPTaR-CINnz7M?87SCYL!9q6bfE3!N_&ZVEB2OgUYR&NQriNYE7e7heoTg9n7#+E z)ymxNVn2?HjSrV}-&%5TGSG=jyp37~Da^{2Pxf8)A5dEJ<5iPwf5q5uutXjjgiX(I zYE{+iahfgEz4kqJJXPYd|5lX!_oq}I4cYdg?Jy_-ZUONKJNb2j%IFe^Uh1T*=BDhf zcRzf>nRldBUKk_}en!=3a${lW2MXD?w&}E9+LR(;up|#80tf!2Fss<1M!rFHchS~S zp~%IG#+hbyi^WpmIcMST0h^R)Ew9uz6?YuSKOA0!&I6^>K!-YBZtt}!zABx{*uX6>=kFhYjtP`Ie;Q^l{#v%SlT-J)PnA4^g*{w(TR+ zNz4jM4+a`)hDDOOjEZ+|{!(!4`?CLM!I6UVo^|-H<9zJKXSUEs`B|JsvO5L?M{f!_ zl`{|P{J|@0?l?zt`$fK%*6?%+x~){U$?D-Q*bXmQJ`L8#d;Rphri*I50WpG+10O7d zK%PJ1+0KQi>+0!|RQ>yf=J{s&T)&Ksy}nZ&P-i4

H1$S7zu1R^u(X=+a-N2qaE)ReLSRVk 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 Date: Thu, 9 Feb 2023 17:49:25 +0000 Subject: [PATCH 094/281] Hound --- .../maya/plugins/publish/validate_arnold_scene_source.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py index ad00502d56..2d6c6e8e14 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -1,8 +1,4 @@ -import os -import types - import maya.cmds as cmds -from mtoa.core import createOptions import pyblish.api from openpype.pipeline.publish import ( From 684c759f516d987e3e59a84584bd295869d02fcc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Feb 2023 18:32:52 +0000 Subject: [PATCH 095/281] Fix capturing from wrong camera. --- .../maya/plugins/publish/extract_playblast.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index e4e44e4770..0140850dc9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,4 +1,5 @@ import os +import json import clique import capture @@ -44,10 +45,6 @@ class ExtractPlayblast(publish.Extractor): # get cameras camera = instance.data['review_camera'] - override_viewport_options = ( - self.capture_preset['Viewport Options'] - ['override_viewport_options'] - ) preset = lib.load_capture_preset(data=self.capture_preset) # Grab capture presets from the project settings capture_presets = self.capture_preset @@ -119,6 +116,9 @@ class ExtractPlayblast(publish.Extractor): pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + override_viewport_options = ( + capture_presets['Viewport Options']['override_viewport_options'] + ) with lib.maintained_time(): filename = preset.get("filename", "%TEMP%") @@ -127,16 +127,21 @@ class ExtractPlayblast(publish.Extractor): # playblast and viewer preset['viewer'] = False - self.log.info('using viewport preset: {}'.format(preset)) - # Update preset with current panel setting # if override_viewport_options is turned off panel = cmds.getPanel(withFocus=True) or "" if not override_viewport_options and "modelPanel" in panel: panel_preset = capture.parse_active_view() + panel_preset.pop("camera") preset.update(panel_preset) cmds.setFocus(panel) + self.log.info( + "Using preset:\n{}".format( + json.dumps(preset, sort_keys=True, indent=4) + ) + ) + path = capture.capture(log=self.log, **preset) cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) From 514ba7e79b55e1aedcfe32c1fe4b949c32af0c13 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 07:25:38 +0000 Subject: [PATCH 096/281] Fix reset_frame_range --- openpype/hosts/maya/api/commands.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index 4a36406632..19ad18d824 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -4,6 +4,7 @@ from maya import cmds from openpype.client import get_asset_by_name, get_project from openpype.pipeline import legacy_io +from . import lib class ToolWindows: @@ -59,25 +60,11 @@ def edit_shader_definitions(): def reset_frame_range(): """Set frame range to current asset""" - # Set FPS first - fps = {15: 'game', - 24: 'film', - 25: 'pal', - 30: 'ntsc', - 48: 'show', - 50: 'palf', - 60: 'ntscf', - 23.98: '23.976fps', - 23.976: '23.976fps', - 29.97: '29.97fps', - 47.952: '47.952fps', - 47.95: '47.952fps', - 59.94: '59.94fps', - 44100: '44100fps', - 48000: '48000fps' - }.get(float(legacy_io.Session.get("AVALON_FPS", 25)), "pal") - cmds.currentUnit(time=fps) + fps = lib.convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + lib.set_scene_fps(fps) # Set frame start/end project_name = legacy_io.active_project() From ff63a91864af8d9dbfdfe940863468a03c0233af Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 10:00:49 +0000 Subject: [PATCH 097/281] Support switching between proxy and non-proxy --- .../maya/plugins/load/load_arnold_standin.py | 25 ++++++++++++------- .../publish/extract_arnold_scene_source.py | 8 +++++- .../publish/validate_arnold_scene_source.py | 12 ++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index e2bb89ed77..bebe40f9a6 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -53,10 +53,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): root = cmds.group(name=label, empty=True) # Set color. - project_name = context["project"]["name"] - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - color = colors.get('ass') + settings = get_project_settings(context["project"]["name"]) + color = settings['maya']['load']['colors'].get('ass') if color is not None: cmds.setAttr(root + ".useOutlinerColor", True) cmds.setAttr( @@ -121,10 +119,6 @@ class ArnoldStandinLoader(load.LoaderPlugin): def _setup_proxy(self, shape, path, namespace): proxy_basename, proxy_path = self._get_proxy_path(path) - if not os.path.exists(proxy_path): - self.log.error("Proxy files do not exist. Skipping proxy setup.") - return path, None - options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") if merge_operator is None: @@ -163,6 +157,12 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) ) + # We setup the string operator no matter whether there is a proxy or + # not. This makes it easier to update since the string operator will + # always be created. Return original path to use for standin. + if not os.path.exists(proxy_path): + return path, string_replace_operator + return proxy_path, string_replace_operator def update(self, container, representation): @@ -180,6 +180,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): path = get_representation_path(representation) proxy_basename, proxy_path = self._get_proxy_path(path) + + # Whether there is proxy or so, we still update the string operator. + # If no proxy exists, the string operator wont replace anything. cmds.setAttr( string_replace_operator + ".match", "resources/" + proxy_basename, @@ -190,7 +193,11 @@ class ArnoldStandinLoader(load.LoaderPlugin): os.path.basename(path), type="string" ) - cmds.setAttr(standin + ".dso", proxy_path, type="string") + + dso_path = path + if os.path.exists(proxy_path): + dso_path = proxy_path + cmds.setAttr(standin + ".dso", dso_path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(path))) cmds.setAttr(standin + ".useFrameExtension", sequence) diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 10943dd810..153a1a513e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -95,6 +95,9 @@ class ExtractArnoldSceneSource(publish.Extractor): ) # Extract proxy. + if not instance.data.get("proxy", []): + return + kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") filenames = self._extract( instance.data["proxy"], attribute_data, kwargs @@ -132,7 +135,6 @@ class ExtractArnoldSceneSource(publish.Extractor): duplicate_nodes = [] for node in nodes: duplicate_transform = cmds.duplicate(node)[0] - delete_bin.append(duplicate_transform) # Discard the children. shapes = cmds.listRelatives(duplicate_transform, shapes=True) @@ -145,7 +147,11 @@ class ExtractArnoldSceneSource(publish.Extractor): duplicate_transform, world=True )[0] + cmds.rename(duplicate_transform, node.split("|")[-1]) + duplicate_transform = "|" + node.split("|")[-1] + duplicate_nodes.append(duplicate_transform) + delete_bin.append(duplicate_transform) with attribute_values(attribute_data): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py index 2d6c6e8e14..3b0ffd52d7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -9,6 +9,9 @@ from openpype.pipeline.publish import ( class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): """Validate Arnold Scene Source. + We require at least 1 root node/parent for the meshes. This is to ensure we + can duplicate the nodes and preserve the names. + If using proxies we need the nodes to share the same names and not be parent to the world. This ends up needing at least two groups with content nodes and proxy nodes in another. @@ -39,9 +42,6 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): return ungrouped_nodes, nodes_by_name, parents def process(self, instance): - if not instance.data["proxy"]: - return - ungrouped_nodes = [] nodes, content_nodes_by_name, content_parents = self._get_nodes_data( @@ -50,7 +50,7 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): ungrouped_nodes.extend(nodes) nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_data( - instance.data["proxy"] + instance.data.get("proxy", []) ) ungrouped_nodes.extend(nodes) @@ -61,6 +61,10 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): "All nodes need to be grouped.".format(ungrouped_nodes) ) + # Proxy validation. + if not instance.data.get("proxy", []): + return + # Validate for content and proxy nodes amount being the same. if len(instance.data["setMembers"]) != len(instance.data["proxy"]): raise PublishValidationError( From 165689463dde7be46804a18166434a5ef8f6ee8b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:20:20 +0100 Subject: [PATCH 098/281] typo --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index aaa2dd444a..c15eadb22f 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -650,7 +650,7 @@ def get_instance_staging_dir(instance): else: project_name = os.getenv("AVALON_PROJECT") - # get customized tempdir path from `OPENPYPE_TEMPDIR` env var + # get customized tempdir path from `OPENPYPE_TMPDIR` env var custom_temp_dir = temporarydir.create_custom_tempdir( project_name, anatomy=anatomy, formating_data=anatomy_data ) From 87f9cf09d77cc8ccec04c2c8dd31905f425ba212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:23:53 +0100 Subject: [PATCH 099/281] Update openpype/pipeline/temporarydir.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/temporarydir.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/temporarydir.py b/openpype/pipeline/temporarydir.py index 31586d82c8..c5805b2dc1 100644 --- a/openpype/pipeline/temporarydir.py +++ b/openpype/pipeline/temporarydir.py @@ -38,10 +38,9 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): } if formating_data is None: # We still don't have `project_code` on Anatomy... - project_doc = anatomy.get_project_doc_from_cache(project_name) data["project"] = { "name": project_name, - "code": project_doc["data"]["code"], + "code": anatomy.project_code, } else: data["project"] = formating_data["project"] From bbd634bcd428b630324b7fbe57324c6eac8bf4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:24:06 +0100 Subject: [PATCH 100/281] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c15eadb22f..423661880c 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -643,7 +643,7 @@ def get_instance_staging_dir(instance): return staging_dir anatomy_data = instance.data.get("anatomy_data") - anatomy = instance.data.get("anatomy") + anatomy = instance.context.data.get("anatomy") if anatomy_data: project_name = anatomy_data["project"]["name"] From af3c0cb951bcd4227ab07cfe174734cd43645b1d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:26:09 +0100 Subject: [PATCH 101/281] pr comments --- openpype/pipeline/publish/lib.py | 4 ++-- openpype/pipeline/{temporarydir.py => tempdir.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename openpype/pipeline/{temporarydir.py => tempdir.py} (100%) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 423661880c..d6e8097690 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -19,7 +19,7 @@ from openpype.settings import ( get_system_settings, ) from openpype.pipeline import ( - temporarydir + tempdir ) from .contants import ( @@ -651,7 +651,7 @@ def get_instance_staging_dir(instance): project_name = os.getenv("AVALON_PROJECT") # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = temporarydir.create_custom_tempdir( + custom_temp_dir = tempdir.create_custom_tempdir( project_name, anatomy=anatomy, formating_data=anatomy_data ) diff --git a/openpype/pipeline/temporarydir.py b/openpype/pipeline/tempdir.py similarity index 100% rename from openpype/pipeline/temporarydir.py rename to openpype/pipeline/tempdir.py From 69937c62858a69d9d42beaeeaa6d23e5073a9446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:27:30 +0100 Subject: [PATCH 102/281] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index d6e8097690..7d3c367c7a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -648,7 +648,7 @@ def get_instance_staging_dir(instance): if anatomy_data: project_name = anatomy_data["project"]["name"] else: - project_name = os.getenv("AVALON_PROJECT") + project_name = instance.context.data["projectName"] # get customized tempdir path from `OPENPYPE_TMPDIR` env var custom_temp_dir = tempdir.create_custom_tempdir( From be0209e4135bea83ffbda230aa23f33651e9cbd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:34:05 +0100 Subject: [PATCH 103/281] refactor in favour of code changes from #4445 https://github.com/ynput/OpenPype/pull/4445 --- openpype/pipeline/publish/lib.py | 10 +--------- openpype/pipeline/tempdir.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 7d3c367c7a..2884dd495f 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -642,18 +642,10 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - anatomy_data = instance.data.get("anatomy_data") anatomy = instance.context.data.get("anatomy") - if anatomy_data: - project_name = anatomy_data["project"]["name"] - else: - project_name = instance.context.data["projectName"] - # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir( - project_name, anatomy=anatomy, formating_data=anatomy_data - ) + custom_temp_dir = tempdir.create_custom_tempdir(anatomy) if custom_temp_dir: staging_dir = os.path.normpath( diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index c5805b2dc1..ff5c58bbc5 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -7,7 +7,7 @@ from openpype.lib import StringTemplate from openpype.pipeline import Anatomy -def create_custom_tempdir(project_name, anatomy=None, formating_data=None): +def create_custom_tempdir(anatomy=None): """ Create custom tempdir Template path formatting is supporting: @@ -17,9 +17,7 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): - project[name | code] Args: - project_name (str): name of project anatomy (openpype.pipeline.Anatomy): Anatomy object - formating_data (dict): formating data used for filling template. Returns: bool | str: formated path or None @@ -31,20 +29,15 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): custom_tempdir = None if "{" in openpype_tempdir: if anatomy is None: - anatomy = Anatomy(project_name) + anatomy = Anatomy() # create base formate data data = { - "root": anatomy.roots - } - if formating_data is None: - # We still don't have `project_code` on Anatomy... - data["project"] = { - "name": project_name, + "root": anatomy.roots, + "project": { + "name": anatomy.project_name, "code": anatomy.project_code, } - else: - data["project"] = formating_data["project"] - + } # path is anatomy template custom_tempdir = StringTemplate.format_template( openpype_tempdir, data).normalized() From 9f4153fbe64ee0f5918354a2723358a760fb571d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 12:27:10 +0000 Subject: [PATCH 104/281] Code cosmetics --- .../hosts/maya/plugins/load/load_arnold_standin.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index bebe40f9a6..6e5fe16bcd 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -94,17 +94,13 @@ class ArnoldStandinLoader(load.LoaderPlugin): def get_next_free_multi_index(self, attr_name): """Find the next unconnected multi index at the input attribute.""" - - start_index = 0 - # Assume a max of 10 million connections - while start_index < 10000000: + for index in range(10000000): connection_info = cmds.connectionInfo( - "{}[{}]".format(attr_name, start_index), + "{}[{}]".format(attr_name, index), sourceFromDestination=True ) if len(connection_info or []) == 0: - return start_index - start_index += 1 + return index def _get_proxy_path(self, path): basename_split = os.path.basename(path).split(".") From 3927dc13af80888375dd1151ff9eef0929a71f9e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 13:42:46 +0100 Subject: [PATCH 105/281] adding back project name --- openpype/pipeline/publish/lib.py | 3 ++- openpype/pipeline/tempdir.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 2884dd495f..cc7f5678f5 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -645,7 +645,8 @@ def get_instance_staging_dir(instance): anatomy = instance.context.data.get("anatomy") # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir(anatomy) + custom_temp_dir = tempdir.create_custom_tempdir( + anatomy.project_name, anatomy) if custom_temp_dir: staging_dir = os.path.normpath( diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index ff5c58bbc5..ab3cc216ef 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -7,7 +7,7 @@ from openpype.lib import StringTemplate from openpype.pipeline import Anatomy -def create_custom_tempdir(anatomy=None): +def create_custom_tempdir(project_name, anatomy=None): """ Create custom tempdir Template path formatting is supporting: @@ -17,7 +17,8 @@ def create_custom_tempdir(anatomy=None): - project[name | code] Args: - anatomy (openpype.pipeline.Anatomy): Anatomy object + project_name (str): project name + anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: bool | str: formated path or None @@ -29,7 +30,7 @@ def create_custom_tempdir(anatomy=None): custom_tempdir = None if "{" in openpype_tempdir: if anatomy is None: - anatomy = Anatomy() + anatomy = Anatomy(project_name) # create base formate data data = { "root": anatomy.roots, From f458fbc9258e97e0e9f340d3e9e59c3eb5b2b820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 13:44:01 +0100 Subject: [PATCH 106/281] Update openpype/pipeline/tempdir.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index ab3cc216ef..6a346f3342 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -21,7 +21,7 @@ def create_custom_tempdir(project_name, anatomy=None): anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: - bool | str: formated path or None + str | None: formated path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 88bda4e1f6bfbe628d80edc76789393dcc9a68a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:24:28 +0100 Subject: [PATCH 107/281] TVPaint host inherit from IPublishHost --- openpype/hosts/tvpaint/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 249326791b..bd6b929f6b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -8,7 +8,7 @@ import requests import pyblish.api from openpype.client import get_project, get_asset_by_name -from openpype.host import HostBase, IWorkfileHost, ILoadHost +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost from openpype.hosts.tvpaint import TVPAINT_ROOT_DIR from openpype.settings import get_current_project_settings from openpype.lib import register_event_callback @@ -58,7 +58,7 @@ instances=2 """ -class TVPaintHost(HostBase, IWorkfileHost, ILoadHost): +class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): name = "tvpaint" def install(self): From 2c0f057a913b9a891f40ffed06959749b90eaae5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:25:59 +0100 Subject: [PATCH 108/281] implemented methods for context data --- openpype/hosts/tvpaint/api/pipeline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index bd6b929f6b..7ab660137a 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -29,6 +29,7 @@ log = logging.getLogger(__name__) METADATA_SECTION = "avalon" SECTION_NAME_CONTEXT = "context" +SECTION_NAME_CREATE_CONTEXT = "create_context" SECTION_NAME_INSTANCES = "instances" SECTION_NAME_CONTAINERS = "containers" # Maximum length of metadata chunk string @@ -93,6 +94,14 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_event_callback("application.launched", self.initial_launch) register_event_callback("application.exit", self.application_exit) + + # --- Create --- + def get_context_data(self): + return get_workfile_metadata(SECTION_NAME_CREATE_CONTEXT, {}) + + def update_context_data(self, data, changes): + return write_workfile_metadata(SECTION_NAME_CREATE_CONTEXT, data) + def open_workfile(self, filepath): george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( filepath.replace("\\", "/") From 85afe4f53ba31678e0d35bbd675c114d7e133b96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:27:22 +0100 Subject: [PATCH 109/281] cleanup of methods --- openpype/hosts/tvpaint/api/pipeline.py | 60 ++++++++++++++------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 7ab660137a..38d3922f3b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -102,6 +102,35 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def update_context_data(self, data, changes): return write_workfile_metadata(SECTION_NAME_CREATE_CONTEXT, data) + def list_instances(self): + """List all created instances from current workfile.""" + return list_instances() + + def write_instances(self, data): + return write_instances(data) + + # --- Legacy Create --- + def remove_instance(self, instance): + """Remove instance from current workfile metadata. + + Implementation for Subset manager tool. + """ + + current_instances = get_workfile_metadata(SECTION_NAME_INSTANCES) + instance_id = instance.get("uuid") + found_idx = None + if instance_id: + for idx, _inst in enumerate(current_instances): + if _inst["uuid"] == instance_id: + found_idx = idx + break + + if found_idx is None: + return + current_instances.pop(found_idx) + write_instances(current_instances) + + # --- Workfile --- def open_workfile(self, filepath): george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( filepath.replace("\\", "/") @@ -134,6 +163,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def get_workfile_extensions(self): return [".tvpp"] + # --- Load --- def get_containers(self): return get_containers() @@ -148,26 +178,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): log.info("Setting up project...") set_context_settings() - def remove_instance(self, instance): - """Remove instance from current workfile metadata. - - Implementation for Subset manager tool. - """ - - current_instances = get_workfile_metadata(SECTION_NAME_INSTANCES) - instance_id = instance.get("uuid") - found_idx = None - if instance_id: - for idx, _inst in enumerate(current_instances): - if _inst["uuid"] == instance_id: - found_idx = idx - break - - if found_idx is None: - return - current_instances.pop(found_idx) - write_instances(current_instances) - def application_exit(self): """Logic related to TimerManager. @@ -186,6 +196,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rest_api_url = "{}/timers_manager/stop_timer".format(webserver_url) requests.post(rest_api_url) + # --- Legacy Publish --- def on_instance_toggle(self, instance, old_value, new_value): """Update instance data in workfile on publish toggle.""" # Review may not have real instance in wokrfile metadata @@ -196,7 +207,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): found_idx = None current_instances = list_instances() for idx, workfile_instance in enumerate(current_instances): - if workfile_instance["uuid"] == instance_id: + if workfile_instance.get("uuid") == instance_id: found_idx = idx break @@ -207,13 +218,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): current_instances[found_idx]["active"] = new_value self.write_instances(current_instances) - def list_instances(self): - """List all created instances from current workfile.""" - return list_instances() - - def write_instances(self, data): - return write_instances(data) - def containerise( name, namespace, members, context, loader, current_containers=None From a17f46486405efe859b626e1633bc4666ac2b709 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:28:13 +0100 Subject: [PATCH 110/281] implement custom context methods --- openpype/hosts/tvpaint/api/pipeline.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 38d3922f3b..85ade41b9b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -18,6 +18,7 @@ from openpype.pipeline import ( register_creator_plugin_path, AVALON_CONTAINER_ID, ) +from openpype.pipeline.context_tools import get_global_context from .lib import ( execute_george, @@ -94,6 +95,40 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_event_callback("application.launched", self.initial_launch) register_event_callback("application.exit", self.application_exit) + def get_current_project_name(self): + """ + Returns: + Union[str, None]: Current project name. + """ + + return self.get_current_context().get("project_name") + + def get_current_asset_name(self): + """ + Returns: + Union[str, None]: Current asset name. + """ + + return self.get_current_context().get("asset_name") + + def get_current_task_name(self): + """ + Returns: + Union[str, None]: Current task name. + """ + + return self.get_current_context().get("task_name") + + def get_current_context(self): + context = get_current_workfile_context() + if not context: + return get_global_context() + + return { + "project_name": context["project"], + "asset_name": context.get("asset"), + "task_name": context.get("task") + } # --- Create --- def get_context_data(self): From 65e7717b6c9bc64f5a832125340bcba0aed50981 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:28:25 +0100 Subject: [PATCH 111/281] use 'get_global_context' on workfile save --- openpype/hosts/tvpaint/api/pipeline.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 85ade41b9b..6a729e39c3 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -175,11 +175,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def save_workfile(self, filepath=None): if not filepath: filepath = self.get_current_workfile() - context = { - "project": legacy_io.Session["AVALON_PROJECT"], - "asset": legacy_io.Session["AVALON_ASSET"], - "task": legacy_io.Session["AVALON_TASK"] - } + context = get_global_context() save_current_workfile_context(context) # Execute george script to save workfile. From b9f22cf2bec8f93d673a3aba9a1cbfd264da69f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:28:45 +0100 Subject: [PATCH 112/281] removed unused method --- openpype/hosts/tvpaint/api/plugin.py | 31 ---------------------------- 1 file changed, 31 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index da456e7067..c7feccd125 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -32,37 +32,6 @@ class Creator(LegacyCreator): dynamic_data["task"] = task_name return dynamic_data - @staticmethod - def are_instances_same(instance_1, instance_2): - """Compare instances but skip keys with unique values. - - During compare are skipped keys that will be 100% sure - different on new instance, like "id". - - Returns: - bool: True if instances are same. - """ - if ( - not isinstance(instance_1, dict) - or not isinstance(instance_2, dict) - ): - return instance_1 == instance_2 - - checked_keys = set() - checked_keys.add("id") - for key, value in instance_1.items(): - if key not in checked_keys: - if key not in instance_2: - return False - if value != instance_2[key]: - return False - checked_keys.add(key) - - for key in instance_2.keys(): - if key not in checked_keys: - return False - return True - def write_instances(self, data): self.log.debug( "Storing instance data to workfile. {}".format(str(data)) From 1cb7c37b9ef51066d2a2c3cc3b243d058303397f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:29:02 +0100 Subject: [PATCH 113/281] implemented base creators for tvpaint --- openpype/hosts/tvpaint/api/plugin.py | 125 +++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index c7feccd125..e6fc087665 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -6,11 +6,136 @@ from openpype.pipeline import ( LoaderPlugin, registered_host, ) +from openpype.pipeline.create import ( + CreatedInstance, + get_subset_name, + AutoCreator, + Creator as NewCreator, +) +from openpype.pipeline.create.creator_plugins import cache_and_get_instances from .lib import get_layers_data from .pipeline import get_current_workfile_context +SHARED_DATA_KEY = "openpype.tvpaint.instances" + + +def _collect_instances(creator): + instances_by_identifier = cache_and_get_instances( + creator, SHARED_DATA_KEY, creator.host.list_instances + ) + for instance_data in instances_by_identifier[creator.identifier]: + instance = CreatedInstance.from_existing(instance_data, creator) + creator._add_instance_to_context(instance) + + +def _update_instances(creator, update_list): + if not update_list: + return + + cur_instances = creator.host.list_instances() + cur_instances_by_id = {} + for instance_data in cur_instances: + instance_id = instance_data.get("instance_id") + if instance_id: + cur_instances_by_id[instance_id] = instance_data + + for instance, changes in update_list: + instance_data = instance.data_to_store() + cur_instance_data = cur_instances_by_id.get(instance.id) + if cur_instance_data is None: + cur_instances.append(instance_data) + continue + for key in set(cur_instance_data) - set(instance_data): + instance_data.pop(key) + instance_data.update(cur_instance_data) + creator.host.write_instances(cur_instances) + + +class TVPaintCreator(NewCreator): + @property + def subset_template_family(self): + return self.family + + def collect_instances(self): + _collect_instances(self) + + def update_instances(self, update_list): + _update_instances(self, update_list) + + def remove_instances(self, instances): + ids_to_remove = { + instance.id + for instance in instances + } + cur_instances = self.host.list_instances() + changed = False + new_instances = [] + for instance_data in cur_instances: + if instance_data.get("instance_id") in ids_to_remove: + changed = True + else: + new_instances.append(instance_data) + + if changed: + self.host.write_instances(new_instances) + + for instance in instances: + self._remove_instance_from_context(instance) + + def get_dynamic_data(self, *args, **kwargs): + # Change asset and name by current workfile context + # TODO use context from 'create_context' + workfile_context = self.host.get_current_context() + asset_name = workfile_context.get("asset") + task_name = workfile_context.get("task") + output = {} + if asset_name: + output["asset"] = asset_name + if task_name: + output["task"] = task_name + return output + + def get_subset_name( + self, + variant, + task_name, + asset_doc, + project_name, + host_name=None, + instance=None + ): + dynamic_data = self.get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, instance + ) + + return get_subset_name( + self.subset_template_family, + variant, + task_name, + asset_doc, + project_name, + host_name, + dynamic_data=dynamic_data, + project_settings=self.project_settings + ) + + def _store_new_instance(self, new_instance): + instances_data = self.host.list_instances() + instances_data.append(new_instance.data_to_store()) + self.host.write_instances(instances_data) + self._add_instance_to_context(new_instance) + + +class TVPaintAutoCreator(AutoCreator): + def collect_instances(self): + _collect_instances(self) + + def update_instances(self, update_list): + _update_instances(self, update_list) + + class Creator(LegacyCreator): def __init__(self, *args, **kwargs): super(Creator, self).__init__(*args, **kwargs) From f984dd8fc5381dd901d69bca6fef5c234ffae1c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:29:15 +0100 Subject: [PATCH 114/281] implemented workfile autocreator --- .../tvpaint/plugins/create/create_workfile.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 openpype/hosts/tvpaint/plugins/create/create_workfile.py diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py new file mode 100644 index 0000000000..3e5cd86852 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -0,0 +1,63 @@ +from openpype.client import get_asset_by_name +from openpype.pipeline import CreatedInstance +from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator + + +class TVPaintWorkfileCreator(TVPaintAutoCreator): + family = "workfile" + identifier = "workfile" + + default_variant = "Main" + + def create(self): + existing_instance = None + for instance in self.create_context.instances: + if instance.creator_identifier == self.identifier: + existing_instance = instance + break + + context = self.host.get_current_context() + host_name = self.host.name + project_name = context["project_name"] + asset_name = context["asset_name"] + task_name = context["task_name"] + + if existing_instance is None: + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + self.default_variant, + task_name, + asset_doc, + project_name, + host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": self.default_variant + } + + new_instance = CreatedInstance( + self.family, subset_name, data, self + ) + instances_data = self.host.list_instances() + instances_data.append(new_instance.data_to_store()) + self.host.write_instances(instances_data) + self._add_instance_to_context(new_instance) + + elif ( + existing_instance["asset"] != asset_name + or existing_instance["task"] != task_name + ): + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + existing_instance["variant"], + task_name, + asset_doc, + project_name, + host_name, + existing_instance + ) + existing_instance["asset"] = asset_name + existing_instance["task"] = task_name + existing_instance["subset"] = subset_name From 2a137622fbe17d588e770e796ad3fdc48401918e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:29:37 +0100 Subject: [PATCH 115/281] base of render layer/pass creators --- .../tvpaint/plugins/create/create_render.py | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 openpype/hosts/tvpaint/plugins/create/create_render.py diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py new file mode 100644 index 0000000000..67337b77a3 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -0,0 +1,309 @@ +from openpype.lib import ( + prepare_template_data, + EnumDef, + TextDef, +) +from openpype.pipeline.create import ( + CreatedInstance, + CreatorError, +) +from openpype.hosts.tvpaint.api.plugin import TVPaintCreator +from openpype.hosts.tvpaint.api.lib import ( + get_layers_data, + get_groups_data, + execute_george_through_file, +) + + +class CreateRenderlayer(TVPaintCreator): + """Mark layer group as one instance.""" + label = "Render Layer" + family = "render" + subset_template_family = "renderLayer" + identifier = "render.layer" + icon = "fa.cube" + + # George script to change color group + rename_script_template = ( + "tv_layercolor \"setcolor\"" + " {clip_id} {group_id} {r} {g} {b} \"{name}\"" + ) + order = 90 + + # Settings + render_pass = "beauty" + + def get_dynamic_data( + self, variant, task_name, asset_doc, project_name, host_name, instance + ): + dynamic_data = super().get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, instance + ) + dynamic_data["renderpass"] = self.render_pass + dynamic_data["renderlayer"] = variant + return dynamic_data + + def _get_selected_group_ids(self): + return { + layer["group_id"] + for layer in get_layers_data() + if layer["selected"] + } + + def create(self, subset_name, instance_data, pre_create_data): + self.log.debug("Query data from workfile.") + + group_id = pre_create_data.get("group_id") + # This creator should run only on one group + if group_id is None or group_id == -1: + selected_groups = self._get_selected_group_ids() + selected_groups.discard(0) + if len(selected_groups) > 1: + raise CreatorError("You have selected more than one group") + + if len(selected_groups) == 0: + raise CreatorError("You don't have selected any group") + group_id = tuple(selected_groups)[0] + + self.log.debug("Querying groups data from workfile.") + groups_data = get_groups_data() + group_item = None + for group_data in groups_data: + if group_data["group_id"] == group_id: + group_item = group_data + + for instance in self.create_context.instances: + if ( + instance.creator_identifier == self.identifier + and instance["creator_attributes"]["group_id"] == group_id + ): + raise CreatorError(( + f"Group \"{group_item.get('name')}\" is already used" + f" by another render layer \"{instance['subset']}\"" + )) + + self.log.debug(f"Selected group id is \"{group_id}\".") + if "creator_attributes" not in instance_data: + instance_data["creator_attributes"] = {} + instance_data["creator_attributes"]["group_id"] = group_id + + self.log.info(f"Subset name is {subset_name}") + new_instance = CreatedInstance( + self.family, + subset_name, + instance_data, + self + ) + self._store_new_instance(new_instance) + + new_group_name = pre_create_data.get("group_name") + if not new_group_name or not group_id: + return + + self.log.debug("Changing name of the group.") + + new_group_name = pre_create_data.get("group_name") + if not new_group_name or group_item["name"] == new_group_name: + return + # Rename TVPaint group (keep color same) + # - groups can't contain spaces + rename_script = self.rename_script_template.format( + clip_id=group_item["clip_id"], + group_id=group_item["group_id"], + r=group_item["red"], + g=group_item["green"], + b=group_item["blue"], + name=new_group_name + ) + execute_george_through_file(rename_script) + + self.log.info(( + f"Name of group with index {group_id}" + f" was changed to \"{new_group_name}\"." + )) + + def get_pre_create_attr_defs(self): + groups_enum = [ + { + "label": group["name"], + "value": group["group_id"] + } + for group in get_groups_data() + if group["name"] + ] + groups_enum.insert(0, {"label": "", "value": -1}) + + return [ + EnumDef( + "group_id", + label="Group", + items=groups_enum + ), + TextDef( + "group_name", + label="New group name", + placeholder="< Keep unchanged >" + ) + ] + + def get_instance_attr_defs(self): + groups_enum = [ + { + "label": group["name"], + "value": group["group_id"] + } + for group in get_groups_data() + if group["name"] + ] + return [ + EnumDef( + "group_id", + label="Group", + items=groups_enum + ) + ] + + +class CreateRenderPass(TVPaintCreator): + icon = "fa.cube" + family = "render" + subset_template_family = "renderPass" + identifier = "render.pass" + label = "Render Pass" + + order = CreateRenderlayer.order + 10 + + def get_dynamic_data( + self, variant, task_name, asset_doc, project_name, host_name, instance + ): + dynamic_data = super().get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, instance + ) + dynamic_data["renderpass"] = variant + dynamic_data["renderlayer"] = "{renderlayer}" + return dynamic_data + + def create(self, subset_name, instance_data, pre_create_data): + render_layer_instance_id = pre_create_data.get( + "render_layer_instance_id" + ) + if not render_layer_instance_id: + raise CreatorError("Missing RenderLayer instance") + + render_layer_instance = self.create_context.instances_by_id.get( + render_layer_instance_id + ) + if render_layer_instance is None: + raise CreatorError(( + "RenderLayer instance was not found" + f" by id \"{render_layer_instance_id}\"" + )) + + group_id = render_layer_instance["creator_attributes"]["group_id"] + self.log.debug("Query data from workfile.") + layers_data = get_layers_data() + + self.log.debug("Checking selection.") + # Get all selected layers and their group ids + selected_layers = [ + layer + for layer in layers_data + if layer["selected"] + ] + + # Raise if nothing is selected + if not selected_layers: + raise CreatorError("Nothing is selected. Please select layers.") + + selected_layer_names = {layer["name"] for layer in selected_layers} + instances_to_remove = [] + for instance in self.create_context.instances: + if instance.creator_identifier != self.identifier: + continue + layer_names = set(instance["layer_names"]) + if not layer_names.intersection(selected_layer_names): + continue + new_layer_names = layer_names - selected_layer_names + if new_layer_names: + instance["layer_names"] = list(new_layer_names) + else: + instances_to_remove.append(instance) + + render_layer = render_layer_instance["variant"] + subset_name_fill_data = {"renderlayer": render_layer} + + # Format dynamic keys in subset name + new_subset_name = subset_name.format( + **prepare_template_data(subset_name_fill_data) + ) + self.log.info(f"New subset name is \"{new_subset_name}\".") + instance_data["layer_names"] = list(selected_layer_names) + new_instance = CreatedInstance( + self.family, + subset_name, + instance_data, + self + ) + instances_data = self._remove_and_filter_instances( + instances_to_remove + ) + instances_data.append(new_instance.data_to_store()) + + self.host.write_instances(instances_data) + self._add_instance_to_context(new_instance) + self._change_layers_group(selected_layers, group_id) + + def _change_layers_group(self, layers, group_id): + filtered_layers = [ + layer + for layer in layers + if layer["group_id"] != group_id + ] + if filtered_layers: + self.log.info(( + "Changing group of " + f"{','.join([l['name'] for l in filtered_layers])}" + f" to {group_id}" + )) + george_lines = [ + f"tv_layercolor \"set\" {layer['layer_id']} {group_id}" + for layer in filtered_layers + ] + execute_george_through_file("\n".join(george_lines)) + + def _remove_and_filter_instances(self, instances_to_remove): + instances_data = self.host.list_instances() + if not instances_to_remove: + return instances_data + + removed_ids = set() + for instance in instances_to_remove: + removed_ids.add(instance.id) + self._remove_instance_from_context(instance) + + return [ + instance_data + for instance_data in instances_data + if instance_data.get("instance_id") not in removed_ids + ] + + def get_pre_create_attr_defs(self): + render_layers = [] + for instance in self.create_context.instances: + if instance.creator_identifier == "render.layer": + render_layers.append({ + "value": instance.id, + "label": instance.label + }) + + if not render_layers: + render_layers.append({"value": None, "label": "N/A"}) + + return [ + EnumDef( + "render_layer_instance_id", + label="Render Layer", + items=render_layers + ) + ] + From bd538e7e70373caafd3067e0acd26acdf8297d28 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 14:30:08 +0100 Subject: [PATCH 116/281] use publisher instead of pyblish pype --- openpype/hosts/tvpaint/api/communication_server.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index 6ac3e6324c..6fd2d69373 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -344,7 +344,7 @@ class QtTVPaintRpc(BaseTVPaintRpc): async def publish_tool(self): log.info("Triggering Publish tool") - item = MainThreadItem(self.tools_helper.show_publish) + item = MainThreadItem(self.tools_helper.show_publisher_tool) self._execute_in_main_thread(item) return @@ -875,10 +875,6 @@ class QtCommunicator(BaseCommunicator): "callback": "library_loader_tool", "label": "Library", "help": "Open library loader tool" - }, { - "callback": "subset_manager_tool", - "label": "Subset Manager", - "help": "Open subset manager tool" }, { "callback": "experimental_tools", "label": "Experimental tools", From 25e4a4b5a3b23fde16f5908b07d81103be99fd03 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 Feb 2023 16:15:55 +0100 Subject: [PATCH 117/281] Added support for multiple install dirs in Deadline SearchDirectoryList returns FIRST existing so if you would have multiple OP install dirs, it won't search for appropriate version in later ones. --- .../custom/plugins/GlobalJobPreLoad.py | 28 ++++++++++--------- .../custom/plugins/OpenPype/OpenPype.py | 23 +++++++-------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 984590ddba..65a3782dfe 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -196,19 +196,21 @@ def get_openpype_versions(dir_list): print(">>> Getting OpenPype executable ...") openpype_versions = [] - install_dir = DirectoryUtils.SearchDirectoryList(dir_list) - if install_dir: - print("--- Looking for OpenPype at: {}".format(install_dir)) - sub_dirs = [ - f.path for f in os.scandir(install_dir) - if f.is_dir() - ] - for subdir in sub_dirs: - version = get_openpype_version_from_path(subdir) - if not version: - continue - print(" - found: {} - {}".format(version, subdir)) - openpype_versions.append((version, subdir)) + # special case of multiple install dirs + for dir_list in dir_list.split(","): + install_dir = DirectoryUtils.SearchDirectoryList(dir_list) + if install_dir: + print("--- Looking for OpenPype at: {}".format(install_dir)) + sub_dirs = [ + f.path for f in os.scandir(install_dir) + if f.is_dir() + ] + for subdir in sub_dirs: + version = get_openpype_version_from_path(subdir) + if not version: + continue + print(" - found: {} - {}".format(version, subdir)) + openpype_versions.append((version, subdir)) return openpype_versions diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py b/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py index 6b0f69d98f..ae31f2e35f 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py @@ -107,17 +107,18 @@ class OpenPypeDeadlinePlugin(DeadlinePlugin): "Scanning for compatible requested " f"version {requested_version}")) dir_list = self.GetConfigEntry("OpenPypeInstallationDirs") - install_dir = DirectoryUtils.SearchDirectoryList(dir_list) - if dir: - sub_dirs = [ - f.path for f in os.scandir(install_dir) - if f.is_dir() - ] - for subdir in sub_dirs: - version = self.get_openpype_version_from_path(subdir) - if not version: - continue - openpype_versions.append((version, subdir)) + for dir_list in dir_list.split(","): + install_dir = DirectoryUtils.SearchDirectoryList(dir_list) + if install_dir: + sub_dirs = [ + f.path for f in os.scandir(install_dir) + if f.is_dir() + ] + for subdir in sub_dirs: + version = self.get_openpype_version_from_path(subdir) + if not version: + continue + openpype_versions.append((version, subdir)) exe_list = self.GetConfigEntry("OpenPypeExecutable") exe = FileUtils.SearchFileList(exe_list) From 75dfd6c3f6b2e37cf9013a692df5385f9af7bddc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 15:19:56 +0000 Subject: [PATCH 118/281] Publish proxy representation. --- .../maya/plugins/load/load_arnold_standin.py | 6 ++-- .../publish/extract_arnold_scene_source.py | 32 ++++++------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 6e5fe16bcd..66e8b69639 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -107,9 +107,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_basename = ( basename_split[0] + "_proxy." + ".".join(basename_split[1:]) ) - proxy_path = "/".join( - [os.path.dirname(path), "resources", proxy_basename] - ) + proxy_path = "/".join([os.path.dirname(path), proxy_basename]) return proxy_basename, proxy_path def _setup_proxy(self, shape, path, namespace): @@ -136,7 +134,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) cmds.setAttr( string_replace_operator + ".match", - "resources/" + proxy_basename, + proxy_basename, type="string" ) cmds.setAttr( diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 153a1a513e..924ac58c40 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -1,5 +1,4 @@ import os -import copy from maya import cmds import arnold @@ -8,7 +7,6 @@ from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( maintained_selection, attribute_values, delete_after ) -from openpype.lib import StringTemplate class ExtractArnoldSceneSource(publish.Extractor): @@ -103,28 +101,16 @@ class ExtractArnoldSceneSource(publish.Extractor): instance.data["proxy"], attribute_data, kwargs ) - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({"ext": "ass"}) - templates = instance.context.data["anatomy"].templates["publish"] - published_filename_without_extension = StringTemplate( - templates["file"] - ).format(template_data).replace(".ass", "_proxy") - transfers = [] - for filename in filenames: - source = os.path.join(staging_dir, filename) - destination = os.path.join( - instance.data["resourcesDir"], - filename.replace( - filename.split(".")[0], - published_filename_without_extension - ) - ) - transfers.append((source, destination)) + representation = { + "name": "proxy", + "ext": "ass", + "files": filenames if len(filenames) > 1 else filenames[0], + "stagingDir": staging_dir, + "frameStart": kwargs["startFrame"], + "outputName": "proxy" + } - for source, destination in transfers: - self.log.debug("Transfer: {} > {}".format(source, destination)) - - instance.data["transfers"] = transfers + instance.data["representations"].append(representation) def _extract(self, nodes, attribute_data, kwargs): self.log.info("Writing: " + kwargs["filename"]) From e7072008af1a316f47ed52ec34823d5a046710e0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 17:33:35 +0100 Subject: [PATCH 119/281] added 'family_filter' argument to 'get_subset_name' --- openpype/pipeline/create/subset_name.py | 39 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/openpype/pipeline/create/subset_name.py b/openpype/pipeline/create/subset_name.py index ed05dd6083..3f0692b46a 100644 --- a/openpype/pipeline/create/subset_name.py +++ b/openpype/pipeline/create/subset_name.py @@ -70,7 +70,8 @@ def get_subset_name( host_name=None, default_template=None, dynamic_data=None, - project_settings=None + project_settings=None, + family_filter=None, ): """Calculate subset name based on passed context and OpenPype settings. @@ -82,23 +83,35 @@ def get_subset_name( That's main reason why so many arguments are required to calculate subset name. + Option to pass family filter was added for special cases when creator or + automated publishing require special subset name template which would be + hard to maintain using its family value. + Why not just pass the right family? -> Family is also used as fill + value and for filtering of publish plugins. + + Todos: + Find better filtering options to avoid requirement of + argument 'family_filter'. + Args: family (str): Instance family. variant (str): In most of the cases it is user input during creation. task_name (str): Task name on which context is instance created. asset_doc (dict): Queried asset document with its tasks in data. Used to get task type. - project_name (str): Name of project on which is instance created. - Important for project settings that are loaded. - host_name (str): One of filtering criteria for template profile - filters. - default_template (str): Default template if any profile does not match - passed context. Constant 'DEFAULT_SUBSET_TEMPLATE' is used if - is not passed. - dynamic_data (dict): Dynamic data specific for a creator which creates - instance. - project_settings (Union[Dict[str, Any], None]): Prepared settings for - project. Settings are queried if not passed. + project_name (Optional[str]): Name of project on which is instance + created. Important for project settings that are loaded. + host_name (Optional[str]): One of filtering criteria for template + profile filters. + default_template (Optional[str]): Default template if any profile does + not match passed context. Constant 'DEFAULT_SUBSET_TEMPLATE' + is used if is not passed. + dynamic_data (Optional[Dict[str, Any]]): Dynamic data specific for + a creator which creates instance. + project_settings (Optional[Union[Dict[str, Any]]]): Prepared settings + for project. Settings are queried if not passed. + family_filter (Optional[str]): Use different family for subset template + filtering. Value of 'family' is used when not passed. """ if not family: @@ -119,7 +132,7 @@ def get_subset_name( template = get_subset_name_template( project_name, - family, + family_filter or family, task_name, task_type, host_name, From 0ab0f323f81aa403789fb4e3b0114c8791d3b8db Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 17:34:03 +0100 Subject: [PATCH 120/281] use new option of family filter in TVPaint creators --- openpype/hosts/tvpaint/api/plugin.py | 7 ++++--- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index e6fc087665..c57baf7212 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -55,7 +55,7 @@ def _update_instances(creator, update_list): class TVPaintCreator(NewCreator): @property - def subset_template_family(self): + def subset_template_family_filter(self): return self.family def collect_instances(self): @@ -111,14 +111,15 @@ class TVPaintCreator(NewCreator): ) return get_subset_name( - self.subset_template_family, + self.family, variant, task_name, asset_doc, project_name, host_name, dynamic_data=dynamic_data, - project_settings=self.project_settings + project_settings=self.project_settings, + family_filter=self.subset_template_family_filter ) def _store_new_instance(self, new_instance): diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 67337b77a3..4050bddd52 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -19,7 +19,7 @@ class CreateRenderlayer(TVPaintCreator): """Mark layer group as one instance.""" label = "Render Layer" family = "render" - subset_template_family = "renderLayer" + subset_template_family_filter = "renderLayer" identifier = "render.layer" icon = "fa.cube" @@ -167,7 +167,7 @@ class CreateRenderlayer(TVPaintCreator): class CreateRenderPass(TVPaintCreator): icon = "fa.cube" family = "render" - subset_template_family = "renderPass" + subset_template_family_filter = "renderPass" identifier = "render.pass" label = "Render Pass" From 3fb87723a0194ad2644b8233dbc8daa7d81c8f01 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 18:29:54 +0100 Subject: [PATCH 121/281] added basic docstring for render creators --- .../tvpaint/plugins/create/create_render.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 4050bddd52..92439f329f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -1,3 +1,34 @@ +"""Render Layer and Passes creators. + +Render layer is main part which is represented by group in TVPaint. All TVPaint +layers marked with that group color are part of the render layer. To be more +specific about some parts of layer it is possible to create sub-sets of layer +which are named passes. Render pass consist of layers in same color group as +render layer but define more specific part. + +For example render layer could be 'Bob' which consist of 5 TVPaint layers. +- Bob has 'head' which consist of 2 TVPaint layers -> Render pass 'head' +- Bob has 'body' which consist of 1 TVPaint layer -> Render pass 'body' +- Bob has 'arm' which consist of 1 TVPaint layer -> Render pass 'arm' +- Last layer does not belong to render pass at all + +Bob will be rendered as 'beauty' of bob (all visible layers in group). +His head will be rendered too but without any other parts. The same for body +and arm. + +What is this good for? Compositing has more power how the renders are used. +Can do transforms on each render pass without need to modify a re-render them +using TVPaint. + +The workflow may hit issues when there are used other blending modes than +default 'color' blend more. In that case it is not recommended to use this +workflow at all as other blend modes may affect all layers in clip which can't +be done. + +Todos: + Add option to extract marked layers and passes as json output format for + AfterEffects. +""" from openpype.lib import ( prepare_template_data, EnumDef, From e426c2c4f0216a3d280f75f9f865de3d3d5ab8d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 18:32:34 +0100 Subject: [PATCH 122/281] added option to mark render instance for review --- .../tvpaint/plugins/create/create_render.py | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 92439f329f..3a89608c7c 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -33,6 +33,7 @@ from openpype.lib import ( prepare_template_data, EnumDef, TextDef, + BoolDef, ) from openpype.pipeline.create import ( CreatedInstance, @@ -48,6 +49,7 @@ from openpype.hosts.tvpaint.api.lib import ( class CreateRenderlayer(TVPaintCreator): """Mark layer group as one instance.""" + label = "Render Layer" family = "render" subset_template_family_filter = "renderLayer" @@ -63,6 +65,7 @@ class CreateRenderlayer(TVPaintCreator): # Settings render_pass = "beauty" + mark_for_review = True def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name, instance @@ -116,7 +119,12 @@ class CreateRenderlayer(TVPaintCreator): self.log.debug(f"Selected group id is \"{group_id}\".") if "creator_attributes" not in instance_data: instance_data["creator_attributes"] = {} - instance_data["creator_attributes"]["group_id"] = group_id + creator_attributes = instance_data["creator_attributes"] + mark_for_review = pre_create_data.get("mark_for_review") + if mark_for_review is None: + mark_for_review = self.mark_for_review + creator_attributes["group_id"] = group_id + creator_attributes["mark_for_review"] = mark_for_review self.log.info(f"Subset name is {subset_name}") new_instance = CreatedInstance( @@ -174,6 +182,11 @@ class CreateRenderlayer(TVPaintCreator): "group_name", label="New group name", placeholder="< Keep unchanged >" + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review ) ] @@ -191,6 +204,11 @@ class CreateRenderlayer(TVPaintCreator): "group_id", label="Group", items=groups_enum + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review ) ] @@ -204,6 +222,9 @@ class CreateRenderPass(TVPaintCreator): order = CreateRenderlayer.order + 10 + # Settings + mark_for_review = True + def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name, instance ): @@ -269,6 +290,18 @@ class CreateRenderPass(TVPaintCreator): ) self.log.info(f"New subset name is \"{new_subset_name}\".") instance_data["layer_names"] = list(selected_layer_names) + if "creator_attributes" not in instance_data: + instance_data["creator_attribtues"] = {} + + creator_attributes = instance_data["creator_attribtues"] + mark_for_review = pre_create_data.get("mark_for_review") + if mark_for_review is None: + mark_for_review = self.mark_for_review + creator_attributes["mark_for_review"] = mark_for_review + creator_attributes["render_layer_instance_id"] = ( + render_layer_instance_id + ) + new_instance = CreatedInstance( self.family, subset_name, @@ -321,7 +354,7 @@ class CreateRenderPass(TVPaintCreator): def get_pre_create_attr_defs(self): render_layers = [] for instance in self.create_context.instances: - if instance.creator_identifier == "render.layer": + if instance.creator_identifier == CreateRenderlayer.identifier: render_layers.append({ "value": instance.id, "label": instance.label @@ -335,6 +368,13 @@ class CreateRenderPass(TVPaintCreator): "render_layer_instance_id", label="Render Layer", items=render_layers + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review ) ] + def get_instance_attr_defs(self): + return self.get_pre_create_attr_defs() From 4a53dce92ffe996900d2914c58ceaae0caedcb30 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 18:32:59 +0100 Subject: [PATCH 123/281] render layer creator changes group ids of render pass layers on save --- .../tvpaint/plugins/create/create_render.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 3a89608c7c..e5d3fa1a59 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -29,6 +29,9 @@ Todos: Add option to extract marked layers and passes as json output format for AfterEffects. """ + +import collections + from openpype.lib import ( prepare_template_data, EnumDef, @@ -212,6 +215,51 @@ class CreateRenderlayer(TVPaintCreator): ) ] + def update_instances(self, update_list): + self._update_renderpass_groups() + + super().update_instances(update_list) + + def _update_renderpass_groups(self): + render_layer_instances = {} + render_pass_instances = collections.defaultdict(list) + + for instance in self.create_context.instances: + if instance.creator_identifier == CreateRenderPass.identifier: + render_layer_id = ( + instance["creator_attributes"]["render_layer_instance_id"] + ) + render_pass_instances[render_layer_id].append(instance) + elif instance.creator_identifier == self.identifier: + render_layer_instances[instance.id] = instance + + if not render_pass_instances or not render_layer_instances: + return + + layers_data = get_layers_data() + layers_by_name = collections.defaultdict(list) + for layer in layers_data: + layers_by_name[layer["name"]].append(layer) + + george_lines = [] + for render_layer_id, instances in render_pass_instances.items(): + render_layer_inst = render_layer_instances.get(render_layer_id) + if render_layer_inst is None: + continue + group_id = render_layer_inst["creator_attributes"]["group_id"] + layer_names = set() + for instance in instances: + layer_names |= set(instance["layer_names"]) + + for layer_name in layer_names: + george_lines.extend( + f"tv_layercolor \"set\" {layer['layer_id']} {group_id}" + for layer in layers_by_name[layer_name] + if layer["group_id"] != group_id + ) + if george_lines: + execute_george_through_file("\n".join(george_lines)) + class CreateRenderPass(TVPaintCreator): icon = "fa.cube" From c537999ffb6de7e9edfd14f226b44c521c809512 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Feb 2023 14:43:08 +0800 Subject: [PATCH 124/281] correct the renderer name for redshift and update arnold render product --- openpype/hosts/max/api/lib_renderproducts.py | 36 +++++++++++++++++++- openpype/hosts/max/api/lib_rendersettings.py | 22 ++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index e3cccff982..c6432412bf 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -47,7 +47,7 @@ class RenderProducts(object): if ( renderer == "ART_Renderer" or - renderer == "Redshift Renderer" or + renderer == "Redshift_Renderer" or renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or @@ -59,11 +59,20 @@ class RenderProducts(object): img_fmt) for render_elem in render_elem_list: full_render_list.append(render_elem) + return full_render_list if renderer == "Arnold": + aov_list = self.arnold_render_product(output_file, + startFrame, + endFrame, + img_fmt) + if aov_list: + for aov in aov_list: + full_render_list.append(aov) return full_render_list + def beauty_render_product(self, folder, startFrame, endFrame, fmt): # get the beauty beauty_frame_range = list() @@ -78,6 +87,31 @@ class RenderProducts(object): return beauty_frame_range # TODO: Get the arnold render product + def arnold_render_product(self, folder, startFrame, endFrame, fmt): + """Get all the Arnold AOVs""" + aovs = list() + + amw = rt.MaxtoAOps.AOVsManagerWindow() + aov_mgr = rt.renderers.current.AOVManager + # Check if there is any aov group set in AOV manager + aov_group_num = len(aov_mgr.drivers) + if aov_group_num < 1: + return + for i in range(aov_group_num): + # get the specific AOV group + for aov in aov_mgr.drivers[i].aov_list: + for f in range(startFrame, endFrame): + render_element = "{0}_{1}.{2}.{3}".format(folder, + str(aov.name), + str(f), + fmt) + render_element = render_element.replace("\\", "/") + aovs.append(render_element) + # close the AOVs manager window + amw.close() + + return aovs + def render_elements_product(self, folder, startFrame, endFrame, fmt): """Get all the render element output files. """ render_dirname = list() diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 2324d743eb..bc9b02bc77 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -87,7 +87,7 @@ class RenderSettings(object): if ( renderer == "ART_Renderer" or - renderer == "Redshift Renderer" or + renderer == "Redshift_Renderer" or renderer == "V_Ray_6_Hotfix_3" or renderer == "V_Ray_GPU_6_Hotfix_3" or renderer == "Default_Scanline_Renderer" or @@ -104,9 +104,25 @@ class RenderSettings(object): render_camera = rt.viewport.GetCamera() arv.setOption("Camera", str(render_camera)) - aovmgr = rt.renderers.current.AOVManager - aovmgr.drivers = "#()" + # TODO: add AOVs and extension + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + setup_cmd = ( + f""" + amw = MaxtoAOps.AOVsManagerWindow() + amw.close() + aovmgr = renderers.current.AOVManager + aovmgr.drivers = #() + img_fmt = "{img_fmt}" + if img_fmt == "png" then driver = ArnoldPNGDriver() + if img_fmt == "jpg" then driver = ArnoldJPEGDriver() + if img_fmt == "exr" then driver = ArnoldEXRDriver() + if img_fmt == "tif" then driver = ArnoldTIFFDriver() + if img_fmt == "tiff" then driver = ArnoldTIFFDriver() + append aovmgr.drivers driver + aovmgr.drivers[1].aov_list = #() + """) + rt.execute(setup_cmd) arv.close() def render_element_layer(self, dir, width, height, ext): From af8278f67ca073386fbcc8dd8f03efd0226366db Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Feb 2023 14:44:24 +0800 Subject: [PATCH 125/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index c6432412bf..b54e2513e1 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -72,7 +72,6 @@ class RenderProducts(object): full_render_list.append(aov) return full_render_list - def beauty_render_product(self, folder, startFrame, endFrame, fmt): # get the beauty beauty_frame_range = list() From 61024a476a36ec0dac03dccfa0bbf3a76edabf8d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Feb 2023 15:02:51 +0800 Subject: [PATCH 126/281] chucksize in create_render --- openpype/hosts/max/plugins/create/create_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 76c10ca4a9..699fc200d3 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -20,6 +20,8 @@ class CreateRender(plugin.MaxCreator): pre_create_data) # type: CreatedInstance container_name = instance.data.get("instance_node") container = rt.getNodeByName(container_name) + # chuckSize for submitting render + instance_data["chunkSize"] = 10 # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container for obj in sel_obj: From b284ad43c04cf05bb604603d211194526f80971c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Feb 2023 17:44:17 +0800 Subject: [PATCH 127/281] not include py script from the unrelated host --- openpype/hosts/maya/plugins/publish/collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index c5fce219fa..0683848c49 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -184,7 +184,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): self.log.info("multipart: {}".format( multipart)) assert exp_files, "no file names were generated, this is bug" - + self.log.info(exp_files) # if we want to attach render to subset, check if we have AOV's # in expectedFiles. If so, raise error as we cannot attach AOV # (considered to be subset on its own) to another subset @@ -319,6 +319,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ) } + # Collect Deadline url if Deadline module is enabled deadline_settings = ( context.data["system_settings"]["modules"]["deadline"] From 6236922e81b40a7ca0a7ed4d325aa97a0de9361e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 Feb 2023 17:45:10 +0800 Subject: [PATCH 128/281] not include py script from the unrelated host --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 0683848c49..b1ad3ca58e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -185,6 +185,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): multipart)) assert exp_files, "no file names were generated, this is bug" self.log.info(exp_files) + # if we want to attach render to subset, check if we have AOV's # in expectedFiles. If so, raise error as we cannot attach AOV # (considered to be subset on its own) to another subset From f56e7bcbf879072c88fcf74944d41cf4366c4e79 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Feb 2023 10:51:21 +0100 Subject: [PATCH 129/281] removed deprecated functions from openpype lib --- openpype/lib/__init__.py | 43 -- openpype/lib/anatomy.py | 38 -- openpype/lib/avalon_context.py | 431 +----------------- openpype/lib/plugin_tools.py | 119 ----- .../tests/test_lib_restructuralization.py | 6 - openpype/tests/test_pyblish_filter.py | 6 +- 6 files changed, 7 insertions(+), 636 deletions(-) delete mode 100644 openpype/lib/anatomy.py diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index b5fb955a84..9eb7724a60 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -82,9 +82,6 @@ from .mongo import ( validate_mongo_connection, OpenPypeMongoConnection ) -from .anatomy import ( - Anatomy -) from .dateutils import ( get_datetime_data, @@ -119,36 +116,19 @@ from .transcoding import ( ) from .avalon_context import ( CURRENT_DOC_SCHEMAS, - PROJECT_NAME_ALLOWED_SYMBOLS, - PROJECT_NAME_REGEX, create_project, - is_latest, - any_outdated, - get_asset, - get_linked_assets, - get_latest_version, - get_system_general_anatomy_data, get_workfile_template_key, get_workfile_template_key_from_context, - get_workdir_data, - get_workdir, - get_workdir_with_workdir_data, get_last_workfile_with_version, get_last_workfile, - create_workfile_doc, - save_workfile_data_to_doc, - get_workfile_doc, - BuildWorkfile, get_creator_by_name, get_custom_workfile_template, - change_timer_to_current_context, - get_custom_workfile_template_by_context, get_custom_workfile_template_by_string_context, get_custom_workfile_template @@ -186,8 +166,6 @@ from .plugin_tools import ( get_subset_name, get_subset_name_with_asset_doc, prepare_template_data, - filter_pyblish_plugins, - set_plugin_attributes_from_settings, source_hash, ) @@ -278,34 +256,17 @@ __all__ = [ "convert_ffprobe_fps_to_float", "CURRENT_DOC_SCHEMAS", - "PROJECT_NAME_ALLOWED_SYMBOLS", - "PROJECT_NAME_REGEX", "create_project", - "is_latest", - "any_outdated", - "get_asset", - "get_linked_assets", - "get_latest_version", - "get_system_general_anatomy_data", "get_workfile_template_key", "get_workfile_template_key_from_context", - "get_workdir_data", - "get_workdir", - "get_workdir_with_workdir_data", "get_last_workfile_with_version", "get_last_workfile", - "create_workfile_doc", - "save_workfile_data_to_doc", - "get_workfile_doc", - "BuildWorkfile", "get_creator_by_name", - "change_timer_to_current_context", - "get_custom_workfile_template_by_context", "get_custom_workfile_template_by_string_context", "get_custom_workfile_template", @@ -338,8 +299,6 @@ __all__ = [ "TaskNotSetError", "get_subset_name", "get_subset_name_with_asset_doc", - "filter_pyblish_plugins", - "set_plugin_attributes_from_settings", "source_hash", "format_file_size", @@ -358,8 +317,6 @@ __all__ = [ "terminal", - "Anatomy", - "get_datetime_data", "get_formatted_current_time", diff --git a/openpype/lib/anatomy.py b/openpype/lib/anatomy.py deleted file mode 100644 index 6d339f058f..0000000000 --- a/openpype/lib/anatomy.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Code related to project Anatomy was moved -to 'openpype.pipeline.anatomy' please change your imports as soon as -possible. File will be probably removed in OpenPype 3.14.* -""" - -import warnings -import functools - - -class AnatomyDeprecatedWarning(DeprecationWarning): - pass - - -def anatomy_deprecated(func): - """Mark functions as deprecated. - - It will result in a warning being emitted when the function is used. - """ - - @functools.wraps(func) - def new_func(*args, **kwargs): - warnings.simplefilter("always", AnatomyDeprecatedWarning) - warnings.warn( - ( - "Deprecated import of 'Anatomy'." - " Class was moved to 'openpype.pipeline.anatomy'." - " Please change your imports of Anatomy in codebase." - ), - category=AnatomyDeprecatedWarning - ) - return func(*args, **kwargs) - return new_func - - -@anatomy_deprecated -def Anatomy(*args, **kwargs): - from openpype.pipeline.anatomy import Anatomy - return Anatomy(*args, **kwargs) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 12f4a5198b..a9ae27cb79 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1,6 +1,5 @@ """Should be used only inside of hosts.""" -import os -import copy + import platform import logging import functools @@ -10,17 +9,12 @@ import six from openpype.client import ( get_project, - get_assets, get_asset_by_name, - get_last_version_by_subset_name, - get_workfile_info, ) from openpype.client.operations import ( CURRENT_ASSET_DOC_SCHEMA, CURRENT_PROJECT_SCHEMA, CURRENT_PROJECT_CONFIG_SCHEMA, - PROJECT_NAME_ALLOWED_SYMBOLS, - PROJECT_NAME_REGEX, ) from .profiles_filtering import filter_profiles from .path_templates import StringTemplate @@ -128,70 +122,6 @@ def with_pipeline_io(func): return wrapped -@deprecated("openpype.pipeline.context_tools.is_representation_from_latest") -def is_latest(representation): - """Return whether the representation is from latest version - - Args: - representation (dict): The representation document from the database. - - Returns: - bool: Whether the representation is of latest version. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import is_representation_from_latest - - return is_representation_from_latest(representation) - - -@deprecated("openpype.pipeline.load.any_outdated_containers") -def any_outdated(): - """Return whether the current scene has any outdated content. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.load import any_outdated_containers - - return any_outdated_containers() - - -@deprecated("openpype.pipeline.context_tools.get_current_project_asset") -def get_asset(asset_name=None): - """ Returning asset document from database by its name. - - Doesn't count with duplicities on asset names! - - Args: - asset_name (str) - - Returns: - (MongoDB document) - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import get_current_project_asset - - return get_current_project_asset(asset_name=asset_name) - - -@deprecated("openpype.pipeline.template_data.get_general_template_data") -def get_system_general_anatomy_data(system_settings=None): - """ - Deprecated: - Function will be removed after release version 3.15.* - """ - from openpype.pipeline.template_data import get_general_template_data - - return get_general_template_data(system_settings) - - @deprecated("openpype.client.get_linked_asset_ids") def get_linked_asset_ids(asset_doc): """Return linked asset ids for `asset_doc` from DB @@ -214,66 +144,6 @@ def get_linked_asset_ids(asset_doc): return get_linked_asset_ids(project_name, asset_doc=asset_doc) -@deprecated("openpype.client.get_linked_assets") -def get_linked_assets(asset_doc): - """Return linked assets for `asset_doc` from DB - - Args: - asset_doc (dict): Asset document from DB - - Returns: - (list) Asset documents of input links for passed asset doc. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline import legacy_io - from openpype.client import get_linked_assets - - project_name = legacy_io.active_project() - - return get_linked_assets(project_name, asset_doc=asset_doc) - - -@deprecated("openpype.client.get_last_version_by_subset_name") -def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): - """Retrieve latest version from `asset_name`, and `subset_name`. - - Do not use if you want to query more than 5 latest versions as this method - query 3 times to mongo for each call. For those cases is better to use - more efficient way, e.g. with help of aggregations. - - Args: - asset_name (str): Name of asset. - subset_name (str): Name of subset. - dbcon (AvalonMongoDB, optional): Avalon Mongo connection with Session. - project_name (str, optional): Find latest version in specific project. - - Returns: - None: If asset, subset or version were not found. - dict: Last version document for entered. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - if not project_name: - if not dbcon: - from openpype.pipeline import legacy_io - - log.debug("Using `legacy_io` for query.") - dbcon = legacy_io - # Make sure is installed - dbcon.install() - - project_name = dbcon.active_project() - - return get_last_version_by_subset_name( - project_name, subset_name, asset_name=asset_name - ) - - @deprecated( "openpype.pipeline.workfile.get_workfile_template_key_from_context") def get_workfile_template_key_from_context( @@ -361,142 +231,6 @@ def get_workfile_template_key( ) -@deprecated("openpype.pipeline.template_data.get_template_data") -def get_workdir_data(project_doc, asset_doc, task_name, host_name): - """Prepare data for workdir template filling from entered information. - - Args: - project_doc (dict): Mongo document of project from MongoDB. - asset_doc (dict): Mongo document of asset from MongoDB. - task_name (str): Task name for which are workdir data preapred. - host_name (str): Host which is used to workdir. This is required - because workdir template may contain `{app}` key. - - Returns: - dict: Data prepared for filling workdir template. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.template_data import get_template_data - - return get_template_data( - project_doc, asset_doc, task_name, host_name - ) - - -@deprecated("openpype.pipeline.workfile.get_workdir_with_workdir_data") -def get_workdir_with_workdir_data( - workdir_data, anatomy=None, project_name=None, template_key=None -): - """Fill workdir path from entered data and project's anatomy. - - It is possible to pass only project's name instead of project's anatomy but - one of them **must** be entered. It is preferred to enter anatomy if is - available as initialization of a new Anatomy object may be time consuming. - - Args: - workdir_data (dict): Data to fill workdir template. - anatomy (Anatomy): Anatomy object for specific project. Optional if - `project_name` is entered. - project_name (str): Project's name. Optional if `anatomy` is entered - otherwise Anatomy object is created with using the project name. - template_key (str): Key of work templates in anatomy templates. If not - passed `get_workfile_template_key_from_context` is used to get it. - dbcon(AvalonMongoDB): Mongo connection. Required only if 'template_key' - and 'project_name' are not passed. - - Returns: - TemplateResult: Workdir path. - - Raises: - ValueError: When both `anatomy` and `project_name` are set to None. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - if not anatomy and not project_name: - raise ValueError(( - "Missing required arguments one of `project_name` or `anatomy`" - " must be entered." - )) - - if not project_name: - project_name = anatomy.project_name - - from openpype.pipeline.workfile import get_workdir_with_workdir_data - - return get_workdir_with_workdir_data( - workdir_data, project_name, anatomy, template_key - ) - - -@deprecated("openpype.pipeline.workfile.get_workdir_with_workdir_data") -def get_workdir( - project_doc, - asset_doc, - task_name, - host_name, - anatomy=None, - template_key=None -): - """Fill workdir path from entered data and project's anatomy. - - Args: - project_doc (dict): Mongo document of project from MongoDB. - asset_doc (dict): Mongo document of asset from MongoDB. - task_name (str): Task name for which are workdir data preapred. - host_name (str): Host which is used to workdir. This is required - because workdir template may contain `{app}` key. In `Session` - is stored under `AVALON_APP` key. - anatomy (Anatomy): Optional argument. Anatomy object is created using - project name from `project_doc`. It is preferred to pass this - argument as initialization of a new Anatomy object may be time - consuming. - template_key (str): Key of work templates in anatomy templates. Default - value is defined in `get_workdir_with_workdir_data`. - - Returns: - TemplateResult: Workdir path. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.workfile import get_workdir - # Output is TemplateResult object which contain useful data - return get_workdir( - project_doc, - asset_doc, - task_name, - host_name, - anatomy, - template_key - ) - - -@deprecated("openpype.pipeline.context_tools.get_template_data_from_session") -def template_data_from_session(session=None): - """ Return dictionary with template from session keys. - - Args: - session (dict, Optional): The Session to use. If not provided use the - currently active global Session. - - Returns: - dict: All available data from session. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import get_template_data_from_session - - return get_template_data_from_session(session) - - @deprecated("openpype.pipeline.context_tools.compute_session_changes") def compute_session_changes( session, task=None, asset=None, app=None, template_key=None @@ -588,133 +322,6 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): return change_current_context(asset, task, template_key) -@deprecated("openpype.client.get_workfile_info") -def get_workfile_doc(asset_id, task_name, filename, dbcon=None): - """Return workfile document for entered context. - - Do not use this method to get more than one document. In that cases use - custom query as this will return documents from database one by one. - - Args: - asset_id (ObjectId): Mongo ID of an asset under which workfile belongs. - task_name (str): Name of task under which the workfile belongs. - filename (str): Name of a workfile. - dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and - `legacy_io` is used if not entered. - - Returns: - dict: Workfile document or None. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - project_name = dbcon.active_project() - return get_workfile_info(project_name, asset_id, task_name, filename) - - -@deprecated -def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): - """Creates or replace workfile document in mongo. - - Do not use this method to update data. This method will remove all - additional data from existing document. - - Args: - asset_doc (dict): Document of asset under which workfile belongs. - task_name (str): Name of task for which is workfile related to. - filename (str): Filename of workfile. - workdir (str): Path to directory where `filename` is located. - dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and - `legacy_io` is used if not entered. - """ - - from openpype.pipeline import Anatomy - from openpype.pipeline.template_data import get_template_data - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - # Filter of workfile document - doc_filter = { - "type": "workfile", - "parent": asset_doc["_id"], - "task_name": task_name, - "filename": filename - } - # Document data are copy of filter - doc_data = copy.deepcopy(doc_filter) - - # Prepare project for workdir data - project_name = dbcon.active_project() - project_doc = get_project(project_name) - workdir_data = get_template_data( - project_doc, asset_doc, task_name, dbcon.Session["AVALON_APP"] - ) - # Prepare anatomy - anatomy = Anatomy(project_name) - # Get workdir path (result is anatomy.TemplateResult) - template_workdir = get_workdir_with_workdir_data( - workdir_data, anatomy - ) - template_workdir_path = str(template_workdir).replace("\\", "/") - - # Replace slashses in workdir path where workfile is located - mod_workdir = workdir.replace("\\", "/") - - # Replace workdir from templates with rootless workdir - rootles_workdir = mod_workdir.replace( - template_workdir_path, - template_workdir.rootless.replace("\\", "/") - ) - - doc_data["schema"] = "pype:workfile-1.0" - doc_data["files"] = ["/".join([rootles_workdir, filename])] - doc_data["data"] = {} - - dbcon.replace_one( - doc_filter, - doc_data, - upsert=True - ) - - -@deprecated -def save_workfile_data_to_doc(workfile_doc, data, dbcon=None): - if not workfile_doc: - # TODO add log message - return - - if not data: - return - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - # Convert data to mongo modification keys/values - # - this is naive implementation which does not expect nested - # dictionaries - set_data = {} - for key, value in data.items(): - new_key = "data.{}".format(key) - set_data[new_key] = value - - # Update workfile document with data - dbcon.update_one( - {"_id": workfile_doc["_id"]}, - {"$set": set_data} - ) - - @deprecated("openpype.pipeline.workfile.BuildWorkfile") def BuildWorkfile(): """Build workfile class was moved to workfile pipeline. @@ -747,38 +354,6 @@ def get_creator_by_name(creator_name, case_sensitive=False): return get_legacy_creator_by_name(creator_name, case_sensitive) -@deprecated -def change_timer_to_current_context(): - """Called after context change to change timers. - - Deprecated: - This method is specific for TimersManager module so please use the - functionality from there. Function will be removed after release - version 3.15.* - """ - - from openpype.pipeline import legacy_io - - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - if not webserver_url: - log.warning("Couldn't find webserver url") - return - - rest_api_url = "{}/timers_manager/start_timer".format(webserver_url) - try: - import requests - except Exception: - log.warning("Couldn't start timer") - return - data = { - "project_name": legacy_io.Session["AVALON_PROJECT"], - "asset_name": legacy_io.Session["AVALON_ASSET"], - "task_name": legacy_io.Session["AVALON_TASK"] - } - - requests.post(rest_api_url, json=data) - - def _get_task_context_data_for_anatomy( project_doc, asset_doc, task_name, anatomy=None ): @@ -800,6 +375,8 @@ def _get_task_context_data_for_anatomy( dict: With Anatomy context data. """ + from openpype.pipeline.template_data import get_general_template_data + if anatomy is None: from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) @@ -840,7 +417,7 @@ def _get_task_context_data_for_anatomy( } } - system_general_data = get_system_general_anatomy_data() + system_general_data = get_general_template_data() data.update(system_general_data) return data diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 1e157dfbfd..10fd3940b8 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -8,7 +8,6 @@ import warnings import functools from openpype.client import get_asset_by_id -from openpype.settings import get_project_settings log = logging.getLogger(__name__) @@ -101,8 +100,6 @@ def get_subset_name_with_asset_doc( is not passed. dynamic_data (dict): Dynamic data specific for a creator which creates instance. - dbcon (AvalonMongoDB): Mongo connection to be able query asset document - if 'asset_doc' is not passed. """ from openpype.pipeline.create import get_subset_name @@ -202,122 +199,6 @@ def prepare_template_data(fill_pairs): return fill_data -@deprecated("openpype.pipeline.publish.lib.filter_pyblish_plugins") -def filter_pyblish_plugins(plugins): - """Filter pyblish plugins by presets. - - This servers as plugin filter / modifier for pyblish. It will load plugin - definitions from presets and filter those needed to be excluded. - - Args: - plugins (dict): Dictionary of plugins produced by :mod:`pyblish-base` - `discover()` method. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.publish.lib import filter_pyblish_plugins - - filter_pyblish_plugins(plugins) - - -@deprecated -def set_plugin_attributes_from_settings( - plugins, superclass, host_name=None, project_name=None -): - """Change attribute values on Avalon plugins by project settings. - - This function should be used only in host context. Modify - behavior of plugins. - - Args: - plugins (list): Plugins discovered by origin avalon discover method. - superclass (object): Superclass of plugin type (e.g. Cretor, Loader). - host_name (str): Name of host for which plugins are loaded and from. - Value from environment `AVALON_APP` is used if not entered. - project_name (str): Name of project for which settings will be loaded. - Value from environment `AVALON_PROJECT` is used if not entered. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - # Function is not used anymore - from openpype.pipeline import LegacyCreator, LoaderPlugin - - # determine host application to use for finding presets - if host_name is None: - host_name = os.environ.get("AVALON_APP") - - if project_name is None: - project_name = os.environ.get("AVALON_PROJECT") - - # map plugin superclass to preset json. Currently supported is load and - # create (LoaderPlugin and LegacyCreator) - plugin_type = None - if superclass is LoaderPlugin or issubclass(superclass, LoaderPlugin): - plugin_type = "load" - elif superclass is LegacyCreator or issubclass(superclass, LegacyCreator): - plugin_type = "create" - - if not host_name or not project_name or plugin_type is None: - msg = "Skipped attributes override from settings." - if not host_name: - msg += " Host name is not defined." - - if not project_name: - msg += " Project name is not defined." - - if plugin_type is None: - msg += " Plugin type is unsupported for class {}.".format( - superclass.__name__ - ) - - print(msg) - return - - print(">>> Finding presets for {}:{} ...".format(host_name, plugin_type)) - - project_settings = get_project_settings(project_name) - plugin_type_settings = ( - project_settings - .get(host_name, {}) - .get(plugin_type, {}) - ) - global_type_settings = ( - project_settings - .get("global", {}) - .get(plugin_type, {}) - ) - if not global_type_settings and not plugin_type_settings: - return - - for plugin in plugins: - plugin_name = plugin.__name__ - - plugin_settings = None - # Look for plugin settings in host specific settings - if plugin_name in plugin_type_settings: - plugin_settings = plugin_type_settings[plugin_name] - - # Look for plugin settings in global settings - elif plugin_name in global_type_settings: - plugin_settings = global_type_settings[plugin_name] - - if not plugin_settings: - continue - - print(">>> We have preset for {}".format(plugin_name)) - for option, value in plugin_settings.items(): - if option == "enabled" and value is False: - setattr(plugin, "active", False) - print(" - is disabled by preset") - else: - setattr(plugin, option, value) - print(" - setting `{}`: `{}`".format(option, value)) - - def source_hash(filepath, *args): """Generate simple identifier for a source file. This is used to identify whether a source file has previously been diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py index c8952e5a1c..669706d470 100644 --- a/openpype/tests/test_lib_restructuralization.py +++ b/openpype/tests/test_lib_restructuralization.py @@ -5,11 +5,9 @@ def test_backward_compatibility(printer): printer("Test if imports still work") try: - from openpype.lib import filter_pyblish_plugins from openpype.lib import execute_hook from openpype.lib import PypeHook - from openpype.lib import get_latest_version from openpype.lib import ApplicationLaunchFailed from openpype.lib import get_ffmpeg_tool_path @@ -18,10 +16,6 @@ def test_backward_compatibility(printer): from openpype.lib import get_version_from_path from openpype.lib import version_up - from openpype.lib import is_latest - from openpype.lib import any_outdated - from openpype.lib import get_asset - from openpype.lib import get_linked_assets from openpype.lib import get_ffprobe_streams from openpype.hosts.fusion.lib import switch_item diff --git a/openpype/tests/test_pyblish_filter.py b/openpype/tests/test_pyblish_filter.py index ea23da26e4..b74784145f 100644 --- a/openpype/tests/test_pyblish_filter.py +++ b/openpype/tests/test_pyblish_filter.py @@ -1,9 +1,9 @@ -from . import lib +import os import pyblish.api import pyblish.util import pyblish.plugin -from openpype.lib import filter_pyblish_plugins -import os +from openpype.pipeline.publish.lib import filter_pyblish_plugins +from . import lib def test_pyblish_plugin_filter_modifier(printer, monkeypatch): From 09dff1629d734e4172cebc4ec9d1134ff36d0f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:36 +0100 Subject: [PATCH 130/281] Update openpype/pipeline/tempdir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 6a346f3342..7e1778539c 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -11,7 +11,7 @@ def create_custom_tempdir(project_name, anatomy=None): """ Create custom tempdir Template path formatting is supporting: - - optional key formating + - optional key formatting - available keys: - root[work | ] - project[name | code] From 8daa8059ccede7693a01a869810acfe0c0fd0cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:45 +0100 Subject: [PATCH 131/281] Update openpype/pipeline/tempdir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 7e1778539c..f26f988557 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -21,7 +21,7 @@ def create_custom_tempdir(project_name, anatomy=None): anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: - str | None: formated path or None + str | None: formatted path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 198050959a4835b436d3fc7e7529f341ed870560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:54 +0100 Subject: [PATCH 132/281] Update openpype/pipeline/tempdir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index f26f988557..4bb62f0afa 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -47,7 +47,7 @@ def create_custom_tempdir(project_name, anatomy=None): # path is absolute custom_tempdir = openpype_tempdir - # create he dir path if it doesnt exists + # create the dir path if it doesn't exists if not os.path.exists(custom_tempdir): try: # create it if it doesnt exists From 053a903662b026837f43308e26df18d049589df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:21 +0100 Subject: [PATCH 133/281] Update openpype/pipeline/tempdir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 4bb62f0afa..88f8296dcf 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -53,6 +53,6 @@ def create_custom_tempdir(project_name, anatomy=None): # create it if it doesnt exists os.makedirs(custom_tempdir) except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) + raise IOError("Path couldn't be created: {}".format(error)) from error return custom_tempdir From 7ea78fee7b0a7b59fdfbe830ea83d66f399350b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:30 +0100 Subject: [PATCH 134/281] Update openpype/pipeline/tempdir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 88f8296dcf..3f9384a7fd 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -50,7 +50,7 @@ def create_custom_tempdir(project_name, anatomy=None): # create the dir path if it doesn't exists if not os.path.exists(custom_tempdir): try: - # create it if it doesnt exists + # create it if it doesn't exists os.makedirs(custom_tempdir) except IOError as error: raise IOError("Path couldn't be created: {}".format(error)) from error From 859863129a033bcde335f4b3af441aecd991716b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:39 +0100 Subject: [PATCH 135/281] Update openpype/pipeline/publish/lib.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cc7f5678f5..2b0d111412 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -620,7 +620,7 @@ def get_instance_staging_dir(instance): It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. - Template formating is supported also with optional keys. Folder is + Template formatting is supported also with optional keys. Folder is created in case it doesnt exists. Available anatomy formatting keys: From 1735d6cc74d75ae732fcd1b0d7832ccd8d89fb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:00 +0100 Subject: [PATCH 136/281] Update openpype/pipeline/publish/lib.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 2b0d111412..27ab523352 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -628,7 +628,7 @@ def get_instance_staging_dir(instance): - project[name | code] Note: - Staging dir does not have to be necessarily in tempdir so be carefull + Staging dir does not have to be necessarily in tempdir so be careful about it's usage. Args: From abe803234ea73ebad6fa12b22932689daa527a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:12 +0100 Subject: [PATCH 137/281] Update website/docs/admin_environment.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- website/docs/admin_environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/admin_environment.md b/website/docs/admin_environment.md index 2cc558b530..1eb755b90b 100644 --- a/website/docs/admin_environment.md +++ b/website/docs/admin_environment.md @@ -9,8 +9,8 @@ import TabItem from '@theme/TabItem'; ## OPENPYPE_TMPDIR: - Custom staging dir directory - - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` - - supported formating keys: + - Supports anatomy keys formatting. ex `{root[work]}/{project[name]}/temp` + - supported formatting keys: - root[work] - project[name | code] From 3885f3cd7c502d04d0ec801cf62e4c047e2a2d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:29 +0100 Subject: [PATCH 138/281] Update website/docs/admin_settings_system.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- website/docs/admin_settings_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index c39cac61f5..d61713ccd5 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -176,4 +176,4 @@ In the image before you can see that we set most of the environment variables in In this example MTOA will automatically will the `MAYA_VERSION`(which is set by Maya Application environment) and `MTOA_VERSION` into the `MTOA` variable. We then use the `MTOA` to set all the other variables needed for it to function within Maya. ![tools](assets/settings/tools_01.png) -All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. +All the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. From 9591d42b84aed7c3321cff5b01b718053593f58d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Feb 2023 12:51:51 +0100 Subject: [PATCH 139/281] spell errors --- openpype/pipeline/publish/lib.py | 6 +++--- openpype/pipeline/tempdir.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 27ab523352..d0a9396a42 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -601,7 +601,7 @@ def context_plugin_should_run(plugin, context): Args: plugin (pyblish.api.Plugin): Plugin with filters. - context (pyblish.api.Context): Pyblish context with insances. + context (pyblish.api.Context): Pyblish context with instances. Returns: bool: Context plugin should run based on valid instances. @@ -621,7 +621,7 @@ def get_instance_staging_dir(instance): It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. Template formatting is supported also with optional keys. Folder is - created in case it doesnt exists. + created in case it doesn't exists. Available anatomy formatting keys: - root[work | ] @@ -629,7 +629,7 @@ def get_instance_staging_dir(instance): Note: Staging dir does not have to be necessarily in tempdir so be careful - about it's usage. + about its usage. Args: instance (pyblish.lib.Instance): Instance for which we want to get diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 3f9384a7fd..3216c596da 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -53,6 +53,7 @@ def create_custom_tempdir(project_name, anatomy=None): # create it if it doesn't exists os.makedirs(custom_tempdir) except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) from error + raise IOError( + "Path couldn't be created: {}".format(error)) from error return custom_tempdir From 21b2cbe34830e27fb02d797443e1e5025fffea8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Feb 2023 15:59:22 +0100 Subject: [PATCH 140/281] better methods propagation --- openpype/hosts/tvpaint/api/plugin.py | 68 +++++++++++++++------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index c57baf7212..d267d87acd 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -21,48 +21,52 @@ from .pipeline import get_current_workfile_context SHARED_DATA_KEY = "openpype.tvpaint.instances" -def _collect_instances(creator): - instances_by_identifier = cache_and_get_instances( - creator, SHARED_DATA_KEY, creator.host.list_instances - ) - for instance_data in instances_by_identifier[creator.identifier]: - instance = CreatedInstance.from_existing(instance_data, creator) - creator._add_instance_to_context(instance) +class TVPaintCreatorCommon: + def _cache_and_get_instances(self): + return cache_and_get_instances( + self, SHARED_DATA_KEY, self.host.list_instances + ) + + def _collect_create_instances(self): + instances_by_identifier = self._cache_and_get_instances() + for instance_data in instances_by_identifier[self.identifier]: + instance = CreatedInstance.from_existing(instance_data, self) + self._add_instance_to_context(instance) -def _update_instances(creator, update_list): - if not update_list: - return + def _update_create_instances(self, update_list): + if not update_list: + return - cur_instances = creator.host.list_instances() - cur_instances_by_id = {} - for instance_data in cur_instances: - instance_id = instance_data.get("instance_id") - if instance_id: - cur_instances_by_id[instance_id] = instance_data + cur_instances = self.host.list_instances() + cur_instances_by_id = {} + for instance_data in cur_instances: + instance_id = instance_data.get("instance_id") + if instance_id: + cur_instances_by_id[instance_id] = instance_data - for instance, changes in update_list: - instance_data = instance.data_to_store() - cur_instance_data = cur_instances_by_id.get(instance.id) - if cur_instance_data is None: - cur_instances.append(instance_data) - continue - for key in set(cur_instance_data) - set(instance_data): - instance_data.pop(key) - instance_data.update(cur_instance_data) - creator.host.write_instances(cur_instances) + for instance, changes in update_list: + instance_data = changes.new_value + cur_instance_data = cur_instances_by_id.get(instance.id) + if cur_instance_data is None: + cur_instances.append(instance_data) + continue + for key in set(cur_instance_data) - set(instance_data): + cur_instance_data.pop(key) + cur_instance_data.update(instance_data) + self.host.write_instances(cur_instances) -class TVPaintCreator(NewCreator): +class TVPaintCreator(NewCreator, TVPaintCreatorCommon): @property def subset_template_family_filter(self): return self.family def collect_instances(self): - _collect_instances(self) + self._collect_create_instances() def update_instances(self, update_list): - _update_instances(self, update_list) + self._update_create_instances(update_list) def remove_instances(self, instances): ids_to_remove = { @@ -129,12 +133,12 @@ class TVPaintCreator(NewCreator): self._add_instance_to_context(new_instance) -class TVPaintAutoCreator(AutoCreator): +class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon): def collect_instances(self): - _collect_instances(self) + self._collect_create_instances() def update_instances(self, update_list): - _update_instances(self, update_list) + self._update_create_instances(update_list) class Creator(LegacyCreator): From db7748995617721d7d564d0097f08ab3fecc141f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Feb 2023 17:24:56 +0100 Subject: [PATCH 141/281] added labels to render pass instances --- .../tvpaint/plugins/create/create_render.py | 70 ++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index e5d3fa1a59..8f7ba121c1 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -273,6 +273,35 @@ class CreateRenderPass(TVPaintCreator): # Settings mark_for_review = True + def collect_instances(self): + instances_by_identifier = self._cache_and_get_instances() + render_layers = { + instance_data["instance_id"]: { + "variant": instance_data["variant"], + "template_data": prepare_template_data({ + "renderlayer": instance_data["variant"] + }) + } + for instance_data in ( + instances_by_identifier[CreateRenderlayer.identifier] + ) + } + + for instance_data in instances_by_identifier[self.identifier]: + render_layer_instance_id = ( + instance_data + .get("creator_attributes", {}) + .get("render_layer_instance_id") + ) + render_layer_info = render_layers.get(render_layer_instance_id) + self.update_instance_labels( + instance_data, + render_layer_info["variant"], + render_layer_info["template_data"] + ) + instance = CreatedInstance.from_existing(instance_data, self) + self._add_instance_to_context(instance) + def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name, instance ): @@ -283,6 +312,29 @@ class CreateRenderPass(TVPaintCreator): dynamic_data["renderlayer"] = "{renderlayer}" return dynamic_data + def update_instance_labels( + self, instance, render_layer_variant, render_layer_data=None + ): + old_label = instance.get("label") + old_group = instance.get("group") + new_label = None + new_group = None + if render_layer_variant is not None: + if render_layer_data is None: + render_layer_data = prepare_template_data({ + "renderlayer": render_layer_variant + }) + try: + new_label = instance["subset"].format(**render_layer_data) + except (KeyError, ValueError): + pass + + new_group = f"{self.get_group_label()} ({render_layer_variant})" + + instance["label"] = new_label + instance["group"] = new_group + return old_group != new_group or old_label != new_label + def create(self, subset_name, instance_data, pre_create_data): render_layer_instance_id = pre_create_data.get( "render_layer_instance_id" @@ -337,6 +389,8 @@ class CreateRenderPass(TVPaintCreator): **prepare_template_data(subset_name_fill_data) ) self.log.info(f"New subset name is \"{new_subset_name}\".") + instance_data["label"] = new_subset_name + instance_data["group"] = f"{self.get_group_label()} ({render_layer})" instance_data["layer_names"] = list(selected_layer_names) if "creator_attributes" not in instance_data: instance_data["creator_attribtues"] = {} @@ -400,14 +454,14 @@ class CreateRenderPass(TVPaintCreator): ] def get_pre_create_attr_defs(self): - render_layers = [] - for instance in self.create_context.instances: - if instance.creator_identifier == CreateRenderlayer.identifier: - render_layers.append({ - "value": instance.id, - "label": instance.label - }) - + render_layers = [ + { + "value": instance.id, + "label": instance.label + } + for instance in self.create_context.instances + if instance.creator_identifier == CreateRenderlayer.identifier + ] if not render_layers: render_layers.append({"value": None, "label": "N/A"}) From 75637cc1a46eca825fee99346424828fe41a83fc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 07:00:21 +0000 Subject: [PATCH 142/281] Strict Error Checking Default Provide default of strict error checking for instances created prior to PR. --- openpype/hosts/maya/plugins/publish/collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index fc297ef612..5bc295a56f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -320,7 +320,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ), "strict_error_checking": render_instance.data.get( - "strict_error_checking") + "strict_error_checking", False + ) } # Collect Deadline url if Deadline module is enabled From 6225083ffe36e197187506e5fd8a59a895e57fb2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 07:06:56 +0000 Subject: [PATCH 143/281] Default Ftrack Family on RenderLayer With default settings, renderlayers in Maya were not being tagged with the Ftrack family leading to confusion when doing reviews. --- openpype/settings/defaults/project_settings/ftrack.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index cdf861df4a..ed646e29fa 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -324,7 +324,8 @@ "animation", "look", "rig", - "camera" + "camera", + "renderlayer" ], "task_types": [], "tasks": [], @@ -494,4 +495,4 @@ "farm_status_profiles": [] } } -} \ No newline at end of file +} From 83c75b8b41be6fc865056b105a75cb9fe5f4dcaa Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 09:08:09 +0000 Subject: [PATCH 144/281] Revert "Default Ftrack Family on RenderLayer" This reverts commit 6225083ffe36e197187506e5fd8a59a895e57fb2. --- openpype/settings/defaults/project_settings/ftrack.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index ed646e29fa..cdf861df4a 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -324,8 +324,7 @@ "animation", "look", "rig", - "camera", - "renderlayer" + "camera" ], "task_types": [], "tasks": [], @@ -495,4 +494,4 @@ "farm_status_profiles": [] } } -} +} \ No newline at end of file From bcf020e4fa2d9bd1c653a96a084691a451c37b4f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 09:19:17 +0000 Subject: [PATCH 145/281] Modify defaults through UI --- openpype/settings/defaults/project_settings/ftrack.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index cdf861df4a..bd761ccd92 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -324,7 +324,8 @@ "animation", "look", "rig", - "camera" + "camera", + "renderlayer" ], "task_types": [], "tasks": [], From d3cc8b59c5add4269908947f5fdad108ec2ade30 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Feb 2023 10:42:54 +0100 Subject: [PATCH 146/281] replaced call to mongo 'dbcon.parenthood' with 'get_representation_parents' function --- openpype/pipeline/load/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index e30923f922..fefdb8537b 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -28,7 +28,6 @@ from openpype.lib import ( TemplateUnsolved, ) from openpype.pipeline import ( - schema, legacy_io, Anatomy, ) @@ -643,7 +642,10 @@ def get_representation_path(representation, root=None, dbcon=None): def path_from_config(): try: - version_, subset, asset, project = dbcon.parenthood(representation) + project_name = dbcon.active_project() + version_, subset, asset, project = get_representation_parents( + project_name, representation + ) except ValueError: log.debug( "Representation %s wasn't found in database, " From 33a7ecd19eacfe237cc7ea68513ff17d692e6a77 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 10:03:01 +0000 Subject: [PATCH 147/281] Code cosmetics --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 5bc295a56f..aa35f687ca 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,7 +42,6 @@ Provides: import re import os import platform -import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup From abe9a2b8951414327c2df4718085dec5ccd20485 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 10:03:12 +0000 Subject: [PATCH 148/281] Default should be True --- openpype/hosts/maya/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index aa35f687ca..f2b5262187 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -319,7 +319,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ), "strict_error_checking": render_instance.data.get( - "strict_error_checking", False + "strict_error_checking", True ) } From 1505478cbdd7def0ab2040d65d09be823b39d958 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 14 Feb 2023 21:05:54 +0800 Subject: [PATCH 149/281] add general 3dsmax settings and add max families into submit publish job --- .../hosts/max/plugins/create/create_render.py | 2 - .../max/plugins/publish/collect_render.py | 22 ++++++-- .../plugins/publish/submit_3dmax_deadline.py | 41 +++++++++++--- .../plugins/publish/submit_publish_job.py | 4 +- .../defaults/project_settings/deadline.json | 11 ++++ .../schema_project_deadline.json | 54 +++++++++++++++++++ 6 files changed, 120 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 699fc200d3..76c10ca4a9 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -20,8 +20,6 @@ class CreateRender(plugin.MaxCreator): pre_create_data) # type: CreatedInstance container_name = instance.data.get("instance_node") container = rt.getNodeByName(container_name) - # chuckSize for submitting render - instance_data["chunkSize"] = 10 # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container for obj in sel_obj: diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 857ac88ea6..5089f107d3 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -6,7 +6,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import legacy_io from openpype.hosts.max.api.lib_renderproducts import RenderProducts - +from openpype.client import get_last_version_by_subset_name class CollectRender(pyblish.api.InstancePlugin): """Collect Render for Deadline""" @@ -30,6 +30,21 @@ class CollectRender(pyblish.api.InstancePlugin): folder = folder.replace("\\", "/") imgFormat = RenderProducts().image_format() + project_name = context.data["projectName"] + asset_doc = context.data["assetEntity"] + asset_id = asset_doc["_id"] + version_doc = get_last_version_by_subset_name(project_name, + instance.name, + asset_id) + + self.log.debug("version_doc: {0}".format(version_doc)) + version_int = 1 + if version_doc: + version_int += int(version_doc["name"]) + + self.log.debug(f"Setting {version_int} to context.") + context.data["version"] = version_int + # setup the plugin as 3dsmax for the internal renderer data = { "subset": instance.name, @@ -39,10 +54,11 @@ class CollectRender(pyblish.api.InstancePlugin): "family": 'maxrender', "families": ['maxrender'], "source": filepath, - "files": render_layer_files, + "expectedFiles": render_layer_files, "plugin": "3dsmax", "frameStart": context.data['frameStart'], - "frameEnd": context.data['frameEnd'] + "frameEnd": context.data['frameEnd'], + "version": version_int } self.log.info("data: {0}".format(data)) instance.data.update(data) diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index 88834e4a91..056e74cf3f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -20,6 +20,12 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): hosts = ["max"] families = ["maxrender"] targets = ["local"] + use_published = True + priority = 50 + chunk_size = 1 + group = None + deadline_pool = None + deadline_pool_secondary = None def process(self, instance): context = instance.context @@ -34,6 +40,24 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): start=int(instance.data["frameStart"]), end=int(instance.data["frameEnd"]) ) + if self.use_published: + for item in context: + if "workfile" in item.data["families"]: + msg = "Workfile (scene) must be published along" + assert item.data["publish"] is True, msg + + template_data = item.data.get("anatomyData") + rep = item.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = context.data["anatomy"].format(template_data) + template_filled = anatomy_filled["publish"]["path"] + filepath = os.path.normpath(template_filled) + + self.log.info( + "Using published scene for render {}".format(filepath) + ) payload = { "JobInfo": { @@ -47,15 +71,17 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): "UserName": deadline_user, "Plugin": instance.data["plugin"], - "Pool": instance.data.get("primaryPool"), - "secondaryPool": instance.data.get("secondaryPool"), + "Group": self.group, + "Pool": self.deadline_pool, + "secondaryPool": self.deadline_pool_secondary, "Frames": frames, - "ChunkSize": instance.data.get("chunkSize", 10), + "ChunkSize": self.chunk_size, + "Priority": instance.data.get("priority", self.priority), "Comment": comment }, "PluginInfo": { # Input - "SceneFile": instance.data["source"], + "SceneFile": filepath, "Version": "2023", "SaveFile": True, # Mandatory for Deadline @@ -94,7 +120,7 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): # frames from Deadline Monitor output_data = {} # need to be fixed - for i, filepath in enumerate(instance.data["files"]): + for i, filepath in enumerate(instance.data["expectedFiles"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/") @@ -129,9 +155,10 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): response = requests.post(url, json=payload) if not response.ok: raise Exception(response.text) - # Store output dir for unified publisher (filesequence) - expected_files = instance.data["files"] + # Store output dir for unified publisher (expectedFilesequence) + expected_files = instance.data["expectedFiles"] self.log.info("exp:{}".format(expected_files)) output_dir = os.path.dirname(expected_files[0]) + instance.data["toBeRenderedOn"] = "deadline" instance.data["outputDir"] = output_dir instance.data["deadlineSubmissionJob"] = response.json() diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7e39a644a2..71934aef93 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -117,10 +117,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): deadline_plugin = "OpenPype" targets = ["local"] - hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] + hosts = ["fusion", "max", "maya", "nuke", "celaction", "aftereffects", "harmony"] families = ["render.farm", "prerender.farm", - "renderlayer", "imagesequence", "vrayscene"] + "renderlayer", "imagesequence", "maxrender", "vrayscene"] aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index ceb0b2e39a..0fab284c66 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -35,6 +35,17 @@ "pluginInfo": {}, "scene_patches": [] }, + "MaxSubmitRenderDeadline": { + "enabled": true, + "optional": false, + "active": true, + "use_published": true, + "priority": 50, + "chunk_size": 10, + "group": "none", + "deadline_pool": "", + "deadline_pool_secondary": "" + }, "NukeSubmitDeadline": { "enabled": true, "optional": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 08a505bd47..afefd3266a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -198,6 +198,60 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "MaxSubmitRenderDeadline", + "label": "3dsMax Submit to Deadline", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "boolean", + "key": "use_published", + "label": "Use Published scene" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "number", + "key": "chunk_size", + "label": "Chunk Size" + }, + { + "type": "text", + "key": "group", + "label": "Group Name" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" + }, + { + "type": "text", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)" + } + ] + }, { "type": "dict", "collapsible": true, From 0dbf111d05843996f64a789e1a938a84ebf585bd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 14 Feb 2023 21:15:46 +0800 Subject: [PATCH 150/281] hound fix --- openpype/hosts/max/plugins/publish/collect_render.py | 1 + .../modules/deadline/plugins/publish/submit_3dmax_deadline.py | 3 ++- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 5089f107d3..55391d40e8 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -8,6 +8,7 @@ from openpype.pipeline import legacy_io from openpype.hosts.max.api.lib_renderproducts import RenderProducts from openpype.client import get_last_version_by_subset_name + class CollectRender(pyblish.api.InstancePlugin): """Collect Render for Deadline""" diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index 056e74cf3f..dec951da7a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -51,7 +51,8 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): template_data["representation"] = rep template_data["ext"] = rep template_data["comment"] = None - anatomy_filled = context.data["anatomy"].format(template_data) + anatomy_data = context.data["anatomy"] + anatomy_filled = anatomy_data.format(template_data) template_filled = anatomy_filled["publish"]["path"] filepath = os.path.normpath(template_filled) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 71934aef93..b70301ab7e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -117,7 +117,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): deadline_plugin = "OpenPype" targets = ["local"] - hosts = ["fusion", "max", "maya", "nuke", "celaction", "aftereffects", "harmony"] + hosts = ["fusion", "max", "maya", "nuke", + "celaction", "aftereffects", "harmony"] families = ["render.farm", "prerender.farm", "renderlayer", "imagesequence", "maxrender", "vrayscene"] From 6bdbdd4337d7d268667cf08f3ef784a8e306184f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 14 Feb 2023 15:32:54 +0000 Subject: [PATCH 151/281] Update openpype/hosts/maya/plugins/load/load_arnold_standin.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- .../hosts/maya/plugins/load/load_arnold_standin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 66e8b69639..ab69d62ef5 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -65,20 +65,20 @@ class ArnoldStandinLoader(load.LoaderPlugin): # Create transform with shape transform_name = label + "_standin" - standinShape = mtoa.ui.arnoldmenu.createStandIn() - standin = cmds.listRelatives(standinShape, parent=True)[0] + standin_shape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standin_shape, parent=True)[0] standin = cmds.rename(standin, transform_name) - standinShape = cmds.listRelatives(standin, shapes=True)[0] + standin_shape = cmds.listRelatives(standin, shapes=True)[0] cmds.parent(standin, root) # Set the standin filepath path, operator = self._setup_proxy( - standinShape, self.fname, namespace + standin_shape, self.fname, namespace ) - cmds.setAttr(standinShape + ".dso", path, type="string") + cmds.setAttr(standin_shape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) - cmds.setAttr(standinShape + ".useFrameExtension", sequence) + cmds.setAttr(standin_shape + ".useFrameExtension", sequence) nodes = [root, standin] if operator is not None: From 5903dbce9e176d26d45681a86efe92f607a512e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Feb 2023 17:03:05 +0100 Subject: [PATCH 152/281] autofill precreate attributes if are not passed --- openpype/pipeline/create/context.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index ba566f93d4..1567acdb79 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -17,6 +17,7 @@ from openpype.lib.attribute_definitions import ( UnknownDef, serialize_attr_defs, deserialize_attr_defs, + get_default_values, ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io @@ -1866,6 +1867,13 @@ class CreateContext: if pre_create_data is None: pre_create_data = {} + precreate_attr_defs = creator.get_pre_create_attr_defs() or [] + # Create default values of precreate data + _pre_create_data = get_default_values(precreate_attr_defs) + # Update passed precreate data to default values + # TODO validate types + _pre_create_data.update(pre_create_data) + subset_name = creator.get_subset_name( variant, task_name, @@ -1881,7 +1889,7 @@ class CreateContext: return creator.create( subset_name, instance_data, - pre_create_data + _pre_create_data ) def _create_with_unified_error( From 8ddcc9c151aea67ae893ee1518b70731fa776deb Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 15 Feb 2023 03:29:31 +0000 Subject: [PATCH 153/281] [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 8dfd638414..6d060656cb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.5" +__version__ = "3.15.1-nightly.6" From ac4078259200edbdf88d58954b1e96e09468e5b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:14:19 +0100 Subject: [PATCH 154/281] fix used constant 'ActiveWindow' -> 'WindowActive' --- openpype/tools/publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 097e289f32..a82f60d5a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -366,7 +366,7 @@ class PublisherWindow(QtWidgets.QDialog): def make_sure_is_visible(self): if self._window_is_visible: - self.setWindowState(QtCore.Qt.ActiveWindow) + self.setWindowState(QtCore.Qt.WindowActive) else: self.show() From bd8f68c6e9574431886a2dd31b8c1620b38a941f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:37:10 +0100 Subject: [PATCH 155/281] changed collect workfile to instance plugin --- .../plugins/publish/collect_workfile.py | 57 ++++--------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index 8c7c8c3899..a3449663f8 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -2,17 +2,15 @@ import os import json import pyblish.api -from openpype.client import get_asset_by_name -from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name - -class CollectWorkfile(pyblish.api.ContextPlugin): +class CollectWorkfile(pyblish.api.InstancePlugin): label = "Collect Workfile" order = pyblish.api.CollectorOrder - 0.4 hosts = ["tvpaint"] + families = ["workfile"] - def process(self, context): + def process(self, instance): + context = instance.context current_file = context.data["currentFile"] self.log.info( @@ -21,49 +19,14 @@ class CollectWorkfile(pyblish.api.ContextPlugin): dirpath, filename = os.path.split(current_file) basename, ext = os.path.splitext(filename) - instance = context.create_instance(name=basename) - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] - - # Get subset name of workfile instance - # Collect asset doc to get asset id - # - not sure if it's good idea to require asset id in - # get_subset_name? - family = "workfile" - asset_name = context.data["workfile_context"]["asset"] - asset_doc = get_asset_by_name(project_name, asset_name) - - # Host name from environment variable - host_name = os.environ["AVALON_APP"] - # Use empty variant value - variant = "" - task_name = legacy_io.Session["AVALON_TASK"] - subset_name = get_subset_name( - family, - variant, - task_name, - asset_doc, - project_name, - host_name, - project_settings=context.data["project_settings"] - ) - - # Create Workfile instance - instance.data.update({ - "subset": subset_name, - "asset": context.data["asset"], - "label": subset_name, - "publish": True, - "family": "workfile", - "families": ["workfile"], - "representations": [{ - "name": ext.lstrip("."), - "ext": ext.lstrip("."), - "files": filename, - "stagingDir": dirpath - }] + instance.data["representations"].append({ + "name": ext.lstrip("."), + "ext": ext.lstrip("."), + "files": filename, + "stagingDir": dirpath }) + self.log.info("Collected workfile instance: {}".format( json.dumps(instance.data, indent=4) )) From d26d9083ce3931ac521ee66a9dd526cf693f5adb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:43:20 +0100 Subject: [PATCH 156/281] fix how context information is returned --- openpype/hosts/tvpaint/api/pipeline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 6a729e39c3..3794bf2e24 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -124,8 +124,11 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): if not context: return get_global_context() + if "project_name" in context: + return context + # This is legacy way how context was stored return { - "project_name": context["project"], + "project_name": context.get("project"), "asset_name": context.get("asset"), "task_name": context.get("task") } From 319c9c60eaae1748cb6662dd9e1c9922d01f17bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:43:39 +0100 Subject: [PATCH 157/281] added autocreator for scene review --- .../tvpaint/plugins/create/create_review.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 openpype/hosts/tvpaint/plugins/create/create_review.py diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py new file mode 100644 index 0000000000..84127eb9b7 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -0,0 +1,64 @@ +from openpype.client import get_asset_by_name +from openpype.pipeline import CreatedInstance +from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator + + +class TVPaintReviewCreator(TVPaintAutoCreator): + family = "review" + identifier = "scene.review" + label = "Review" + + default_variant = "Main" + + def create(self): + existing_instance = None + for instance in self.create_context.instances: + if instance.creator_identifier == self.identifier: + existing_instance = instance + break + + context = self.host.get_current_context() + host_name = self.host.name + project_name = context["project_name"] + asset_name = context["asset_name"] + task_name = context["task_name"] + + if existing_instance is None: + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + self.default_variant, + task_name, + asset_doc, + project_name, + host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": self.default_variant + } + + new_instance = CreatedInstance( + self.family, subset_name, data, self + ) + instances_data = self.host.list_instances() + instances_data.append(new_instance.data_to_store()) + self.host.write_instances(instances_data) + self._add_instance_to_context(new_instance) + + elif ( + existing_instance["asset"] != asset_name + or existing_instance["task"] != task_name + ): + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + existing_instance["variant"], + task_name, + asset_doc, + project_name, + host_name, + existing_instance + ) + existing_instance["asset"] = asset_name + existing_instance["task"] = task_name + existing_instance["subset"] = subset_name From 8bad64e59f2fb2b10a912d57e451531da8099f7b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:43:48 +0100 Subject: [PATCH 158/281] change lael of workfile creator --- openpype/hosts/tvpaint/plugins/create/create_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index 3e5cd86852..e421fbc3f8 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -6,6 +6,7 @@ from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator class TVPaintWorkfileCreator(TVPaintAutoCreator): family = "workfile" identifier = "workfile" + label = "Workfile" default_variant = "Main" From dae4094d8a3c3ec00f60c13e344a4e211b105ce1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:44:50 +0100 Subject: [PATCH 159/281] change how instance frames are calculated --- .../publish/collect_instance_frames.py | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py index d5b79758ad..5eb702a1da 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -1,37 +1,34 @@ import pyblish.api -class CollectOutputFrameRange(pyblish.api.ContextPlugin): +class CollectOutputFrameRange(pyblish.api.InstancePlugin): """Collect frame start/end from context. When instances are collected context does not contain `frameStart` and `frameEnd` keys yet. They are collected in global plugin `CollectContextEntities`. """ + label = "Collect output frame range" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.4999 hosts = ["tvpaint"] + families = ["review", "render"] - def process(self, context): - for instance in context: - frame_start = instance.data.get("frameStart") - frame_end = instance.data.get("frameEnd") - if frame_start is not None and frame_end is not None: - self.log.debug( - "Instance {} already has set frames {}-{}".format( - str(instance), frame_start, frame_end - ) - ) - return + def process(self, instance): + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + return - frame_start = context.data.get("frameStart") - frame_end = context.data.get("frameEnd") + context = instance.context - instance.data["frameStart"] = frame_start - instance.data["frameEnd"] = frame_end - - self.log.info( - "Set frames {}-{} on instance {} ".format( - frame_start, frame_end, str(instance) - ) + frame_start = asset_doc["data"]["frameStart"] + frame_end = frame_start + ( + context.data["sceneMarkOut"] - context.data["sceneMarkIn"] + ) + instance.data["frameStart"] = frame_start + instance.data["frameEnd"] = frame_end + self.log.info( + "Set frames {}-{} on instance {} ".format( + frame_start, frame_end, instance.data["subset"] ) + ) From 71d9ceb91e5eddc9daa82da27cb21d55d04e3ffb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:45:57 +0100 Subject: [PATCH 160/281] fix collect workfile data --- .../plugins/publish/collect_workfile_data.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py index 8fe71a4a46..95a5cd77bd 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py @@ -65,9 +65,9 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Collect and store current context to have reference current_context = { - "project": legacy_io.Session["AVALON_PROJECT"], - "asset": legacy_io.Session["AVALON_ASSET"], - "task": legacy_io.Session["AVALON_TASK"] + "project_name": context.data["projectName"], + "asset_name": context.data["asset"], + "task_name": context.data["task"] } context.data["previous_context"] = current_context self.log.debug("Current context is: {}".format(current_context)) @@ -76,25 +76,31 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): self.log.info("Collecting workfile context") workfile_context = get_current_workfile_context() + if "project" in workfile_context: + workfile_context = { + "project_name": workfile_context.get("project"), + "asset_name": workfile_context.get("asset"), + "task_name": workfile_context.get("task"), + } # Store workfile context to pyblish context context.data["workfile_context"] = workfile_context if workfile_context: # Change current context with context from workfile key_map = ( - ("AVALON_ASSET", "asset"), - ("AVALON_TASK", "task") + ("AVALON_ASSET", "asset_name"), + ("AVALON_TASK", "task_name") ) for env_key, key in key_map: legacy_io.Session[env_key] = workfile_context[key] os.environ[env_key] = workfile_context[key] self.log.info("Context changed to: {}".format(workfile_context)) - asset_name = workfile_context["asset"] - task_name = workfile_context["task"] + asset_name = workfile_context["asset_name"] + task_name = workfile_context["task_name"] else: - asset_name = current_context["asset"] - task_name = current_context["task"] + asset_name = current_context["asset_name"] + task_name = current_context["task_name"] # Handle older workfiles or workfiles without metadata self.log.warning(( "Workfile does not contain information about context." @@ -103,6 +109,7 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): # Store context asset name context.data["asset"] = asset_name + context.data["task"] = task_name self.log.info( "Context is set to Asset: \"{}\" and Task: \"{}\"".format( asset_name, task_name From b3e34fce324603cf955f3fcf8dca795995796d98 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:46:17 +0100 Subject: [PATCH 161/281] fix few validators --- .../publish/validate_render_pass_group.py | 1 - .../publish/validate_workfile_metadata.py | 17 +++++++++++++---- .../publish/validate_workfile_project_name.py | 7 +++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py b/openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py index 0fbfca6c56..2a3173c698 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py @@ -85,6 +85,5 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin): ), "expected_group": correct_group["name"], "layer_names": ", ".join(invalid_layer_names) - } ) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py index d66ae50c60..b38231e208 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_metadata.py @@ -1,5 +1,9 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError, registered_host +from openpype.pipeline import ( + PublishXmlValidationError, + PublishValidationError, + registered_host, +) class ValidateWorkfileMetadataRepair(pyblish.api.Action): @@ -27,13 +31,18 @@ class ValidateWorkfileMetadata(pyblish.api.ContextPlugin): actions = [ValidateWorkfileMetadataRepair] - required_keys = {"project", "asset", "task"} + required_keys = {"project_name", "asset_name", "task_name"} def process(self, context): workfile_context = context.data["workfile_context"] if not workfile_context: - raise AssertionError( - "Current workfile is missing whole metadata about context." + raise PublishValidationError( + "Current workfile is missing whole metadata about context.", + "Missing context", + ( + "Current workfile is missing metadata about task." + " To fix this issue save the file using Workfiles tool." + ) ) missing_keys = [] diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py index 0f25f2f7be..2ed5afa11c 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py @@ -1,4 +1,3 @@ -import os import pyblish.api from openpype.pipeline import PublishXmlValidationError @@ -16,15 +15,15 @@ class ValidateWorkfileProjectName(pyblish.api.ContextPlugin): def process(self, context): workfile_context = context.data.get("workfile_context") # If workfile context is missing than project is matching to - # `AVALON_PROJECT` value for 100% + # global project if not workfile_context: self.log.info( "Workfile context (\"workfile_context\") is not filled." ) return - workfile_project_name = workfile_context["project"] - env_project_name = os.environ["AVALON_PROJECT"] + workfile_project_name = workfile_context["project_name"] + env_project_name = context.data["projectName"] if workfile_project_name == env_project_name: self.log.info(( "Both workfile project and environment project are same. {}" From 8551b1f1b3969ef1fb2db815883169175dd1b0ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:59:06 +0100 Subject: [PATCH 162/281] change how extract sequence works --- .../tvpaint/plugins/publish/extract_sequence.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 78074f720c..f2856c72a9 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -6,6 +6,7 @@ from PIL import Image import pyblish.api +from openpype.pipeline.publish import KnownPublishError from openpype.hosts.tvpaint.api.lib import ( execute_george, execute_george_through_file, @@ -24,8 +25,7 @@ from openpype.hosts.tvpaint.lib import ( class ExtractSequence(pyblish.api.Extractor): label = "Extract Sequence" hosts = ["tvpaint"] - families = ["review", "renderPass", "renderLayer", "renderScene"] - families_to_review = ["review"] + families = ["review", "render"] # Modifiable with settings review_bg = [255, 255, 255, 255] @@ -136,7 +136,7 @@ class ExtractSequence(pyblish.api.Extractor): # Fill tags and new families from project settings tags = [] - if family_lowered in self.families_to_review: + if family_lowered == "review": tags.append("review") # Sequence of one frame @@ -162,10 +162,6 @@ class ExtractSequence(pyblish.api.Extractor): instance.data["representations"].append(new_repre) - if family_lowered in ("renderpass", "renderlayer", "renderscene"): - # Change family to render - instance.data["family"] = "render" - if not thumbnail_fullpath: return @@ -259,7 +255,7 @@ class ExtractSequence(pyblish.api.Extractor): output_filepaths_by_frame_idx[frame_idx] = filepath if not os.path.exists(filepath): - raise AssertionError( + raise KnownPublishError( "Output was not rendered. File was not found {}".format( filepath ) From ff7b3004ad953253afbc8e3a9640aa99a24f1c47 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 11:00:10 +0100 Subject: [PATCH 163/281] added validator for duplicated usage of render layer groups --- .../help/validate_render_layer_group.xml | 18 +++++ .../publish/validate_render_layer_group.py | 74 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 openpype/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml create mode 100644 openpype/hosts/tvpaint/plugins/publish/validate_render_layer_group.py diff --git a/openpype/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml b/openpype/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml new file mode 100644 index 0000000000..a95387356f --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/help/validate_render_layer_group.xml @@ -0,0 +1,18 @@ + + + +Overused Color group +## One Color group is used by multiple Render Layers + +Single color group used by multiple Render Layers would cause clashes of rendered TVPaint layers. The same layers would be used for output files of both groups. + +### Missing layer names + +{groups_information} + +### How to repair? + +Refresh, go to 'Publish' tab and go through Render Layers and change their groups to not clash each other. If you reach limit of TVPaint color groups there is nothing you can do about it to fix the issue. + + + diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_render_layer_group.py b/openpype/hosts/tvpaint/plugins/publish/validate_render_layer_group.py new file mode 100644 index 0000000000..bb0a9a4ffe --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/validate_render_layer_group.py @@ -0,0 +1,74 @@ +import collections +import pyblish.api +from openpype.pipeline import PublishXmlValidationError + + +class ValidateRenderLayerGroups(pyblish.api.ContextPlugin): + """Validate group ids of renderLayer subsets. + + Validate that there are not 2 render layers using the same group. + """ + + label = "Validate Render Layers Group" + order = pyblish.api.ValidatorOrder + 0.1 + + def process(self, context): + # Prepare layers + render_layers_by_group_id = collections.defaultdict(list) + for instance in context: + families = instance.data.get("families") + if not families or "renderLayer" not in families: + continue + + group_id = instance.data["creator_attributes"]["group_id"] + render_layers_by_group_id[group_id].append(instance) + + duplicated_instances = [] + for group_id, instances in render_layers_by_group_id.items(): + if len(instances) > 1: + duplicated_instances.append((group_id, instances)) + + if not duplicated_instances: + return + + # Exception message preparations + groups_data = context.data["groupsData"] + groups_by_id = { + group["group_id"]: group + for group in groups_data + } + + per_group_msgs = [] + groups_information_lines = [] + for group_id, instances in duplicated_instances: + group = groups_by_id[group_id] + group_label = "Group \"{}\" ({})".format( + group["name"], + group["group_id"], + ) + line_join_subset_names = "\n".join([ + f" - {instance['subset']}" + for instance in instances + ]) + joined_subset_names = ", ".join([ + f"\"{instance['subset']}\"" + for instance in instances + ]) + per_group_msgs.append( + "{} < {} >".format(group_label, joined_subset_names) + ) + groups_information_lines.append( + "{}\n{}".format(group_label, line_join_subset_names) + ) + + # Raise an error + raise PublishXmlValidationError( + self, + ( + "More than one Render Layer is using the same TVPaint" + " group color. {}" + ).format(" | ".join(per_group_msgs)), + formatting_data={ + "groups_information": "\n".join(groups_information_lines) + } + ) From b405cac45cc082cfd30e735a0b8e8ee0859aae74 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 11:07:59 +0100 Subject: [PATCH 164/281] implemented new collection of render instances --- .../plugins/publish/collect_instances.py | 280 ------------------ .../publish/collect_render_instances.py | 87 ++++++ 2 files changed, 87 insertions(+), 280 deletions(-) delete mode 100644 openpype/hosts/tvpaint/plugins/publish/collect_instances.py create mode 100644 openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py deleted file mode 100644 index ae1326a5bd..0000000000 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ /dev/null @@ -1,280 +0,0 @@ -import json -import copy -import pyblish.api - -from openpype.client import get_asset_by_name -from openpype.pipeline import legacy_io -from openpype.pipeline.create import get_subset_name - - -class CollectInstances(pyblish.api.ContextPlugin): - label = "Collect Instances" - order = pyblish.api.CollectorOrder - 0.4 - hosts = ["tvpaint"] - - def process(self, context): - workfile_instances = context.data["workfileInstances"] - - self.log.debug("Collected ({}) instances:\n{}".format( - len(workfile_instances), - json.dumps(workfile_instances, indent=4) - )) - - filtered_instance_data = [] - # Backwards compatibility for workfiles that already have review - # instance in metadata. - review_instance_exist = False - for instance_data in workfile_instances: - family = instance_data["family"] - if family == "review": - review_instance_exist = True - - elif family not in ("renderPass", "renderLayer"): - self.log.info("Unknown family \"{}\". Skipping {}".format( - family, json.dumps(instance_data, indent=4) - )) - continue - - filtered_instance_data.append(instance_data) - - # Fake review instance if review was not found in metadata families - if not review_instance_exist: - filtered_instance_data.append( - self._create_review_instance_data(context) - ) - - for instance_data in filtered_instance_data: - instance_data["fps"] = context.data["sceneFps"] - - # Conversion from older instances - # - change 'render_layer' to 'renderlayer' - render_layer = instance_data.get("instance_data") - if not render_layer: - # Render Layer has only variant - if instance_data["family"] == "renderLayer": - render_layer = instance_data.get("variant") - - # Backwards compatibility for renderPasses - elif "render_layer" in instance_data: - render_layer = instance_data["render_layer"] - - if render_layer: - instance_data["renderlayer"] = render_layer - - # Store workfile instance data to instance data - instance_data["originData"] = copy.deepcopy(instance_data) - # Global instance data modifications - # Fill families - family = instance_data["family"] - families = [family] - if family != "review": - families.append("review") - # Add `review` family for thumbnail integration - instance_data["families"] = families - - # Instance name - subset_name = instance_data["subset"] - name = instance_data.get("name", subset_name) - instance_data["name"] = name - instance_data["label"] = "{} [{}-{}]".format( - name, - context.data["sceneMarkIn"] + 1, - context.data["sceneMarkOut"] + 1 - ) - - active = instance_data.get("active", True) - instance_data["active"] = active - instance_data["publish"] = active - # Add representations key - instance_data["representations"] = [] - - # Different instance creation based on family - instance = None - if family == "review": - # Change subset name of review instance - - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] - - # Collect asset doc to get asset id - # - not sure if it's good idea to require asset id in - # get_subset_name? - asset_name = context.data["workfile_context"]["asset"] - asset_doc = get_asset_by_name(project_name, asset_name) - - # Host name from environment variable - host_name = context.data["hostName"] - # Use empty variant value - variant = "" - task_name = legacy_io.Session["AVALON_TASK"] - new_subset_name = get_subset_name( - family, - variant, - task_name, - asset_doc, - project_name, - host_name, - project_settings=context.data["project_settings"] - ) - instance_data["subset"] = new_subset_name - - instance = context.create_instance(**instance_data) - - instance.data["layers"] = copy.deepcopy( - context.data["layersData"] - ) - - elif family == "renderLayer": - instance = self.create_render_layer_instance( - context, instance_data - ) - elif family == "renderPass": - instance = self.create_render_pass_instance( - context, instance_data - ) - - if instance is None: - continue - - any_visible = False - for layer in instance.data["layers"]: - if layer["visible"]: - any_visible = True - break - - instance.data["publish"] = any_visible - - self.log.debug("Created instance: {}\n{}".format( - instance, json.dumps(instance.data, indent=4) - )) - - def _create_review_instance_data(self, context): - """Fake review instance data.""" - - return { - "family": "review", - "asset": context.data["asset"], - # Dummy subset name - "subset": "reviewMain" - } - - def create_render_layer_instance(self, context, instance_data): - name = instance_data["name"] - # Change label - subset_name = instance_data["subset"] - - # Backwards compatibility - # - subset names were not stored as final subset names during creation - if "variant" not in instance_data: - instance_data["label"] = "{}_Beauty".format(name) - - # Change subset name - # Final family of an instance will be `render` - new_family = "render" - task_name = legacy_io.Session["AVALON_TASK"] - new_subset_name = "{}{}_{}_Beauty".format( - new_family, task_name.capitalize(), name - ) - instance_data["subset"] = new_subset_name - self.log.debug("Changed subset name \"{}\"->\"{}\"".format( - subset_name, new_subset_name - )) - - # Get all layers for the layer - layers_data = context.data["layersData"] - group_id = instance_data["group_id"] - group_layers = [] - for layer in layers_data: - if layer["group_id"] == group_id: - group_layers.append(layer) - - if not group_layers: - # Should be handled here? - self.log.warning(( - f"Group with id {group_id} does not contain any layers." - f" Instance \"{name}\" not created." - )) - return None - - instance_data["layers"] = group_layers - - return context.create_instance(**instance_data) - - def create_render_pass_instance(self, context, instance_data): - pass_name = instance_data["pass"] - self.log.info( - "Creating render pass instance. \"{}\"".format(pass_name) - ) - # Change label - render_layer = instance_data["renderlayer"] - - # Backwards compatibility - # - subset names were not stored as final subset names during creation - if "variant" not in instance_data: - instance_data["label"] = "{}_{}".format(render_layer, pass_name) - # Change subset name - # Final family of an instance will be `render` - new_family = "render" - old_subset_name = instance_data["subset"] - task_name = legacy_io.Session["AVALON_TASK"] - new_subset_name = "{}{}_{}_{}".format( - new_family, task_name.capitalize(), render_layer, pass_name - ) - instance_data["subset"] = new_subset_name - self.log.debug("Changed subset name \"{}\"->\"{}\"".format( - old_subset_name, new_subset_name - )) - - layers_data = context.data["layersData"] - layers_by_name = { - layer["name"]: layer - for layer in layers_data - } - - if "layer_names" in instance_data: - layer_names = instance_data["layer_names"] - else: - # Backwards compatibility - # - not 100% working as it was found out that layer ids can't be - # used as unified identifier across multiple workstations - layers_by_id = { - layer["layer_id"]: layer - for layer in layers_data - } - layer_ids = instance_data["layer_ids"] - layer_names = [] - for layer_id in layer_ids: - layer = layers_by_id.get(layer_id) - if layer: - layer_names.append(layer["name"]) - - if not layer_names: - raise ValueError(( - "Metadata contain old way of storing layers information." - " It is not possible to identify layers to publish with" - " these data. Please remove Render Pass instances with" - " Subset manager and use Creator tool to recreate them." - )) - - render_pass_layers = [] - for layer_name in layer_names: - layer = layers_by_name.get(layer_name) - # NOTE This is kind of validation before validators? - if not layer: - self.log.warning( - f"Layer with name {layer_name} was not found." - ) - continue - - render_pass_layers.append(layer) - - if not render_pass_layers: - name = instance_data["name"] - self.log.warning( - f"None of the layers from the RenderPass \"{name}\"" - " exist anymore. Instance not created." - ) - return None - - instance_data["layers"] = render_pass_layers - return context.create_instance(**instance_data) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py new file mode 100644 index 0000000000..34bb5aba24 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -0,0 +1,87 @@ +import copy +import pyblish.api +from openpype.lib import prepare_template_data + + +class CollectRenderInstances(pyblish.api.InstancePlugin): + label = "Collect Render Instances" + order = pyblish.api.CollectorOrder - 0.4 + hosts = ["tvpaint"] + families = ["render", "review"] + + def process(self, instance): + context = instance.context + creator_identifier = instance.data["creator_identifier"] + if creator_identifier == "render.layer": + self._collect_data_for_render_layer(instance) + + elif creator_identifier == "render.pass": + self._collect_data_for_render_pass(instance) + + else: + if creator_identifier == "scene.review": + self._collect_data_for_review(instance) + return + + subset_name = instance.data["subset"] + instance.data["name"] = subset_name + instance.data["label"] = "{} [{}-{}]".format( + subset_name, + context.data["sceneMarkIn"] + 1, + context.data["sceneMarkOut"] + 1 + ) + + def _collect_data_for_render_layer(self, instance): + instance.data["families"].append("renderLayer") + creator_attributes = instance.data["creator_attributes"] + group_id = creator_attributes["group_id"] + if creator_attributes["mark_for_review"]: + instance.data["families"].append("review") + + layers_data = instance.context.data["layersData"] + instance.data["layers"] = [ + copy.deepcopy(layer) + for layer in layers_data + if layer["group_id"] == group_id + ] + + def _collect_data_for_render_pass(self, instance): + instance.data["families"].append("renderPass") + + layer_names = set(instance.data["layer_names"]) + layers_data = instance.context.data["layersData"] + + creator_attributes = instance.data["creator_attributes"] + if creator_attributes["mark_for_review"]: + instance.data["families"].append("review") + + instance.data["layers"] = [ + copy.deepcopy(layer) + for layer in layers_data + if layer["name"] in layer_names + ] + + render_layer_data = None + render_layer_id = creator_attributes["render_layer_instance_id"] + for in_data in instance.context.data["workfileInstances"]: + if ( + in_data["creator_identifier"] == "render.layer" + and in_data["instance_id"] == render_layer_id + ): + render_layer_data = in_data + break + + instance.data["renderLayerData"] = copy.deepcopy(render_layer_data) + # Invalid state + if render_layer_data is None: + return + render_layer_name = render_layer_data["variant"] + subset_name = instance.data["subset"] + instance.data["subset"] = subset_name.format( + **prepare_template_data({"renderlayer": render_layer_name}) + ) + + def _collect_data_for_review(self, instance): + instance.data["layers"] = copy.deepcopy( + instance.context.data["layersData"] + ) From db75420899f3419e937dbf7b88d6f89e8dc42ae1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 11:09:49 +0100 Subject: [PATCH 165/281] removed legacy creators --- .../plugins/create/create_render_layer.py | 231 ------------------ .../plugins/create/create_render_pass.py | 167 ------------- 2 files changed, 398 deletions(-) delete mode 100644 openpype/hosts/tvpaint/plugins/create/create_render_layer.py delete mode 100644 openpype/hosts/tvpaint/plugins/create/create_render_pass.py diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py deleted file mode 100644 index 009b69c4f1..0000000000 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ /dev/null @@ -1,231 +0,0 @@ -from openpype.lib import prepare_template_data -from openpype.pipeline import CreatorError -from openpype.hosts.tvpaint.api import ( - plugin, - CommunicationWrapper -) -from openpype.hosts.tvpaint.api.lib import ( - get_layers_data, - get_groups_data, - execute_george_through_file, -) -from openpype.hosts.tvpaint.api.pipeline import list_instances - - -class CreateRenderlayer(plugin.Creator): - """Mark layer group as one instance.""" - name = "render_layer" - label = "RenderLayer" - family = "renderLayer" - icon = "cube" - defaults = ["Main"] - - rename_group = True - render_pass = "beauty" - - rename_script_template = ( - "tv_layercolor \"setcolor\"" - " {clip_id} {group_id} {r} {g} {b} \"{name}\"" - ) - - dynamic_subset_keys = [ - "renderpass", "renderlayer", "render_pass", "render_layer", "group" - ] - - @classmethod - def get_dynamic_data( - cls, variant, task_name, asset_id, project_name, host_name - ): - dynamic_data = super(CreateRenderlayer, cls).get_dynamic_data( - variant, task_name, asset_id, project_name, host_name - ) - # Use render pass name from creator's plugin - dynamic_data["renderpass"] = cls.render_pass - # Add variant to render layer - dynamic_data["renderlayer"] = variant - # Change family for subset name fill - dynamic_data["family"] = "render" - - # TODO remove - Backwards compatibility for old subset name templates - # - added 2022/04/28 - dynamic_data["render_pass"] = dynamic_data["renderpass"] - dynamic_data["render_layer"] = dynamic_data["renderlayer"] - - return dynamic_data - - @classmethod - def get_default_variant(cls): - """Default value for variant in Creator tool. - - Method checks if TVPaint implementation is running and tries to find - selected layers from TVPaint. If only one is selected it's name is - returned. - - Returns: - str: Default variant name for Creator tool. - """ - # Validate that communication is initialized - if CommunicationWrapper.communicator: - # Get currently selected layers - layers_data = get_layers_data() - - selected_layers = [ - layer - for layer in layers_data - if layer["selected"] - ] - # Return layer name if only one is selected - if len(selected_layers) == 1: - return selected_layers[0]["name"] - - # Use defaults - if cls.defaults: - return cls.defaults[0] - return None - - def process(self): - self.log.debug("Query data from workfile.") - instances = list_instances() - layers_data = get_layers_data() - - self.log.debug("Checking for selection groups.") - # Collect group ids from selection - group_ids = set() - for layer in layers_data: - if layer["selected"]: - group_ids.add(layer["group_id"]) - - # Raise if there is no selection - if not group_ids: - raise CreatorError("Nothing is selected.") - - # This creator should run only on one group - if len(group_ids) > 1: - raise CreatorError("More than one group is in selection.") - - group_id = tuple(group_ids)[0] - # If group id is `0` it is `default` group which is invalid - if group_id == 0: - raise CreatorError( - "Selection is not in group. Can't mark selection as Beauty." - ) - - self.log.debug(f"Selected group id is \"{group_id}\".") - self.data["group_id"] = group_id - - group_data = get_groups_data() - group_name = None - for group in group_data: - if group["group_id"] == group_id: - group_name = group["name"] - break - - if group_name is None: - raise AssertionError( - "Couldn't find group by id \"{}\"".format(group_id) - ) - - subset_name_fill_data = { - "group": group_name - } - - family = self.family = self.data["family"] - - # Fill dynamic key 'group' - subset_name = self.data["subset"].format( - **prepare_template_data(subset_name_fill_data) - ) - self.data["subset"] = subset_name - - # Check for instances of same group - existing_instance = None - existing_instance_idx = None - # Check if subset name is not already taken - same_subset_instance = None - same_subset_instance_idx = None - for idx, instance in enumerate(instances): - if instance["family"] == family: - if instance["group_id"] == group_id: - existing_instance = instance - existing_instance_idx = idx - elif instance["subset"] == subset_name: - same_subset_instance = instance - same_subset_instance_idx = idx - - if ( - same_subset_instance_idx is not None - and existing_instance_idx is not None - ): - break - - if same_subset_instance_idx is not None: - if self._ask_user_subset_override(same_subset_instance): - instances.pop(same_subset_instance_idx) - else: - return - - if existing_instance is not None: - self.log.info( - f"Beauty instance for group id {group_id} already exists" - ", overriding" - ) - instances[existing_instance_idx] = self.data - else: - instances.append(self.data) - - self.write_instances(instances) - - if not self.rename_group: - self.log.info("Group rename function is turned off. Skipping") - return - - self.log.debug("Querying groups data from workfile.") - groups_data = get_groups_data() - - self.log.debug("Changing name of the group.") - selected_group = None - for group_data in groups_data: - if group_data["group_id"] == group_id: - selected_group = group_data - - # Rename TVPaint group (keep color same) - # - groups can't contain spaces - new_group_name = self.data["variant"].replace(" ", "_") - rename_script = self.rename_script_template.format( - clip_id=selected_group["clip_id"], - group_id=selected_group["group_id"], - r=selected_group["red"], - g=selected_group["green"], - b=selected_group["blue"], - name=new_group_name - ) - execute_george_through_file(rename_script) - - self.log.info( - f"Name of group with index {group_id}" - f" was changed to \"{new_group_name}\"." - ) - - def _ask_user_subset_override(self, instance): - from qtpy import QtCore - from qtpy.QtWidgets import QMessageBox - - title = "Subset \"{}\" already exist".format(instance["subset"]) - text = ( - "Instance with subset name \"{}\" already exists." - "\n\nDo you want to override existing?" - ).format(instance["subset"]) - - dialog = QMessageBox() - dialog.setWindowFlags( - dialog.windowFlags() - | QtCore.Qt.WindowStaysOnTopHint - ) - dialog.setWindowTitle(title) - dialog.setText(text) - dialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - dialog.setDefaultButton(QMessageBox.Yes) - dialog.exec_() - if dialog.result() == QMessageBox.Yes: - return True - return False diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py deleted file mode 100644 index a44cb29f20..0000000000 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ /dev/null @@ -1,167 +0,0 @@ -from openpype.pipeline import CreatorError -from openpype.lib import prepare_template_data -from openpype.hosts.tvpaint.api import ( - plugin, - CommunicationWrapper -) -from openpype.hosts.tvpaint.api.lib import get_layers_data -from openpype.hosts.tvpaint.api.pipeline import list_instances - - -class CreateRenderPass(plugin.Creator): - """Render pass is combination of one or more layers from same group. - - Requirement to create Render Pass is to have already created beauty - instance. Beauty instance is used as base for subset name. - """ - name = "render_pass" - label = "RenderPass" - family = "renderPass" - icon = "cube" - defaults = ["Main"] - - dynamic_subset_keys = [ - "renderpass", "renderlayer", "render_pass", "render_layer" - ] - - @classmethod - def get_dynamic_data( - cls, variant, task_name, asset_id, project_name, host_name - ): - dynamic_data = super(CreateRenderPass, cls).get_dynamic_data( - variant, task_name, asset_id, project_name, host_name - ) - dynamic_data["renderpass"] = variant - dynamic_data["family"] = "render" - - # TODO remove - Backwards compatibility for old subset name templates - # - added 2022/04/28 - dynamic_data["render_pass"] = dynamic_data["renderpass"] - - return dynamic_data - - @classmethod - def get_default_variant(cls): - """Default value for variant in Creator tool. - - Method checks if TVPaint implementation is running and tries to find - selected layers from TVPaint. If only one is selected it's name is - returned. - - Returns: - str: Default variant name for Creator tool. - """ - # Validate that communication is initialized - if CommunicationWrapper.communicator: - # Get currently selected layers - layers_data = get_layers_data() - - selected_layers = [ - layer - for layer in layers_data - if layer["selected"] - ] - # Return layer name if only one is selected - if len(selected_layers) == 1: - return selected_layers[0]["name"] - - # Use defaults - if cls.defaults: - return cls.defaults[0] - return None - - def process(self): - self.log.debug("Query data from workfile.") - instances = list_instances() - layers_data = get_layers_data() - - self.log.debug("Checking selection.") - # Get all selected layers and their group ids - group_ids = set() - selected_layers = [] - for layer in layers_data: - if layer["selected"]: - selected_layers.append(layer) - group_ids.add(layer["group_id"]) - - # Raise if nothing is selected - if not selected_layers: - raise CreatorError("Nothing is selected.") - - # Raise if layers from multiple groups are selected - if len(group_ids) != 1: - raise CreatorError("More than one group is in selection.") - - group_id = tuple(group_ids)[0] - self.log.debug(f"Selected group id is \"{group_id}\".") - - # Find beauty instance for selected layers - beauty_instance = None - for instance in instances: - if ( - instance["family"] == "renderLayer" - and instance["group_id"] == group_id - ): - beauty_instance = instance - break - - # Beauty is required for this creator so raise if was not found - if beauty_instance is None: - raise CreatorError("Beauty pass does not exist yet.") - - subset_name = self.data["subset"] - - subset_name_fill_data = {} - - # Backwards compatibility - # - beauty may be created with older creator where variant was not - # stored - if "variant" not in beauty_instance: - render_layer = beauty_instance["name"] - else: - render_layer = beauty_instance["variant"] - - subset_name_fill_data["renderlayer"] = render_layer - subset_name_fill_data["render_layer"] = render_layer - - # Format dynamic keys in subset name - new_subset_name = subset_name.format( - **prepare_template_data(subset_name_fill_data) - ) - self.data["subset"] = new_subset_name - self.log.info(f"New subset name is \"{new_subset_name}\".") - - family = self.data["family"] - variant = self.data["variant"] - - self.data["group_id"] = group_id - self.data["pass"] = variant - self.data["renderlayer"] = render_layer - - # Collect selected layer ids to be stored into instance - layer_names = [layer["name"] for layer in selected_layers] - self.data["layer_names"] = layer_names - - # Check if same instance already exists - existing_instance = None - existing_instance_idx = None - for idx, instance in enumerate(instances): - if ( - instance["family"] == family - and instance["group_id"] == group_id - and instance["pass"] == variant - ): - existing_instance = instance - existing_instance_idx = idx - break - - if existing_instance is not None: - self.log.info( - f"Render pass instance for group id {group_id}" - f" and name \"{variant}\" already exists, overriding." - ) - instances[existing_instance_idx] = self.data - else: - instances.append(self.data) - - self.write_instances(instances) From 3e6a120eaa808bf69e0bdbe893b0dd8c21c6939a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 11:46:20 +0100 Subject: [PATCH 166/281] fix default settings of nuke --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index cd8ea02272..2ec2028219 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -1,7 +1,6 @@ { "general": { "menu": { - "create": "ctrl+alt+c", "publish": "ctrl+alt+p", "load": "ctrl+alt+l", "manage": "ctrl+alt+m", @@ -246,6 +245,7 @@ "sourcetype": "python", "title": "Gizmo Note", "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", + "icon": "", "shortcut": "" } ] From df532268a2e05b6b48074336453ab3e18b86e08f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:00:15 +0100 Subject: [PATCH 167/281] add family to instance data --- openpype/pipeline/create/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 1567acdb79..79c9805604 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1884,6 +1884,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, + "family": self.family, "variant": variant } return creator.create( From fb93780640ed5de588cb11499ccd68d1f6a91d75 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:08:45 +0100 Subject: [PATCH 168/281] use family form creator --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 79c9805604..89eec52676 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1884,7 +1884,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, - "family": self.family, + "family": creator.family, "variant": variant } return creator.create( From 6097a9627607a70bb91d977274da8bd9f39742b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:14:53 +0100 Subject: [PATCH 169/281] added option to use 'subset_template_family_filter' in all tvpaint creators --- openpype/hosts/tvpaint/api/plugin.py | 62 +++++++++++++++------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index d267d87acd..397e4295f5 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -22,6 +22,10 @@ SHARED_DATA_KEY = "openpype.tvpaint.instances" class TVPaintCreatorCommon: + @property + def subset_template_family_filter(self): + return self.family + def _cache_and_get_instances(self): return cache_and_get_instances( self, SHARED_DATA_KEY, self.host.list_instances @@ -56,12 +60,33 @@ class TVPaintCreatorCommon: cur_instance_data.update(instance_data) self.host.write_instances(cur_instances) + def _custom_get_subset_name( + self, + variant, + task_name, + asset_doc, + project_name, + host_name=None, + instance=None + ): + dynamic_data = self.get_dynamic_data( + variant, task_name, asset_doc, project_name, host_name, instance + ) + + return get_subset_name( + self.family, + variant, + task_name, + asset_doc, + project_name, + host_name, + dynamic_data=dynamic_data, + project_settings=self.project_settings, + family_filter=self.subset_template_family_filter + ) + class TVPaintCreator(NewCreator, TVPaintCreatorCommon): - @property - def subset_template_family_filter(self): - return self.family - def collect_instances(self): self._collect_create_instances() @@ -101,30 +126,8 @@ class TVPaintCreator(NewCreator, TVPaintCreatorCommon): output["task"] = task_name return output - def get_subset_name( - self, - variant, - task_name, - asset_doc, - project_name, - host_name=None, - instance=None - ): - dynamic_data = self.get_dynamic_data( - variant, task_name, asset_doc, project_name, host_name, instance - ) - - return get_subset_name( - self.family, - variant, - task_name, - asset_doc, - project_name, - host_name, - dynamic_data=dynamic_data, - project_settings=self.project_settings, - family_filter=self.subset_template_family_filter - ) + def get_subset_name(self, *args, **kwargs): + return self._custom_get_subset_name(*args, **kwargs) def _store_new_instance(self, new_instance): instances_data = self.host.list_instances() @@ -140,6 +143,9 @@ class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon): def update_instances(self, update_list): self._update_create_instances(update_list) + def get_subset_name(self, *args, **kwargs): + return self._custom_get_subset_name(*args, **kwargs) + class Creator(LegacyCreator): def __init__(self, *args, **kwargs): From 222b39f024e5fcf8890145f08eba8298282ddb39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Feb 2023 12:22:13 +0100 Subject: [PATCH 170/281] nuke: adding back Create shortcut it was removed accidentally --- openpype/settings/defaults/project_settings/nuke.json | 3 ++- .../schemas/projects_schema/schema_project_nuke.json | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 2ec2028219..d475c337d9 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -1,6 +1,7 @@ { "general": { "menu": { + "create": "ctrl+alt+c", "publish": "ctrl+alt+p", "load": "ctrl+alt+l", "manage": "ctrl+alt+m", @@ -532,4 +533,4 @@ "profiles": [] }, "filters": {} -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index b1a8cc1812..26c64e6219 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -17,6 +17,11 @@ "key": "menu", "label": "OpenPype Menu shortcuts", "children": [ + { + "type": "text", + "key": "create", + "label": "Create..." + }, { "type": "text", "key": "publish", @@ -288,4 +293,4 @@ "name": "schema_publish_gui_filter" } ] -} \ No newline at end of file +} From d727ec1f5f58b1fcd18401cd9ad03b9a690a2cff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:23:24 +0100 Subject: [PATCH 171/281] added screne render auto creator --- .../tvpaint/plugins/create/create_render.py | 125 +++++++++++++++++- .../publish/collect_render_instances.py | 22 +++ .../plugins/publish/collect_scene_render.py | 114 ---------------- 3 files changed, 146 insertions(+), 115 deletions(-) delete mode 100644 openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 8f7ba121c1..2b693d4bc0 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -32,6 +32,7 @@ Todos: import collections +from openpype.client import get_asset_by_name from openpype.lib import ( prepare_template_data, EnumDef, @@ -42,7 +43,10 @@ from openpype.pipeline.create import ( CreatedInstance, CreatorError, ) -from openpype.hosts.tvpaint.api.plugin import TVPaintCreator +from openpype.hosts.tvpaint.api.plugin import ( + TVPaintCreator, + TVPaintAutoCreator, +) from openpype.hosts.tvpaint.api.lib import ( get_layers_data, get_groups_data, @@ -480,3 +484,122 @@ class CreateRenderPass(TVPaintCreator): def get_instance_attr_defs(self): return self.get_pre_create_attr_defs() + + +class TVPaintSceneRenderCreator(TVPaintAutoCreator): + family = "render" + subset_template_family_filter = "renderScene" + identifier = "render.scene" + label = "Scene Render" + + # Settings + default_variant = "Main" + default_pass_name = "beauty" + mark_for_review = True + + def get_dynamic_data(self, variant, *args, **kwargs): + dynamic_data = super().get_dynamic_data(variant, *args, **kwargs) + dynamic_data["renderpass"] = "{renderpass}" + dynamic_data["renderlayer"] = variant + return dynamic_data + + def _create_new_instance(self): + context = self.host.get_current_context() + host_name = self.host.name + project_name = context["project_name"] + asset_name = context["asset_name"] + task_name = context["task_name"] + + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + self.default_variant, + task_name, + asset_doc, + project_name, + host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": self.default_variant, + "creator_attributes": { + "render_pass_name": self.default_pass_name, + "mark_for_review": True + }, + "label": self._get_label( + subset_name, + self.default_pass_name + ) + } + + new_instance = CreatedInstance( + self.family, subset_name, data, self + ) + instances_data = self.host.list_instances() + instances_data.append(new_instance.data_to_store()) + self.host.write_instances(instances_data) + self._add_instance_to_context(new_instance) + return new_instance + + def create(self): + existing_instance = None + for instance in self.create_context.instances: + if instance.creator_identifier == self.identifier: + existing_instance = instance + break + + if existing_instance is None: + return self._create_new_instance() + + context = self.host.get_current_context() + host_name = self.host.name + project_name = context["project_name"] + asset_name = context["asset_name"] + task_name = context["task_name"] + + if ( + existing_instance["asset"] != asset_name + or existing_instance["task"] != task_name + ): + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + existing_instance["variant"], + task_name, + asset_doc, + project_name, + host_name, + existing_instance + ) + existing_instance["asset"] = asset_name + existing_instance["task"] = task_name + existing_instance["subset"] = subset_name + + existing_instance["label"] = self._get_label( + existing_instance["subset"], + existing_instance["creator_attributes"]["render_pass_name"] + ) + + + + def _get_label(self, subset_name, render_layer_name): + return subset_name.format(**prepare_template_data({ + "renderlayer": render_layer_name + })) + + def get_instance_attr_defs(self): + return [ + TextDef( + "render_pass_name", + label="Pass Name", + default=self.default_pass_name, + tooltip=( + "Value is calculated during publishing and UI will update" + " label after refresh." + ) + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review + ) + ] \ No newline at end of file diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index 34bb5aba24..ba89deac5d 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -18,6 +18,9 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): elif creator_identifier == "render.pass": self._collect_data_for_render_pass(instance) + elif creator_identifier == "render.scene": + self._collect_data_for_render_scene(instance) + else: if creator_identifier == "scene.review": self._collect_data_for_review(instance) @@ -81,6 +84,25 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): **prepare_template_data({"renderlayer": render_layer_name}) ) + def _collect_data_for_render_scene(self, instance): + instance.data["families"].append("renderScene") + + creator_attributes = instance.data["creator_attributes"] + if creator_attributes["mark_for_review"]: + instance.data["families"].append("review") + + instance.data["layers"] = copy.deepcopy( + instance.context.data["layersData"] + ) + + render_pass_name = ( + instance.data["creator_attributes"]["render_pass_name"] + ) + subset_name = instance.data["subset"] + instance.data["subset"] = subset_name.format( + **prepare_template_data({"renderpass": render_pass_name}) + ) + def _collect_data_for_review(self, instance): instance.data["layers"] = copy.deepcopy( instance.context.data["layersData"] diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py deleted file mode 100644 index 92a2815ba0..0000000000 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ /dev/null @@ -1,114 +0,0 @@ -import json -import copy -import pyblish.api - -from openpype.client import get_asset_by_name -from openpype.pipeline.create import get_subset_name - - -class CollectRenderScene(pyblish.api.ContextPlugin): - """Collect instance which renders whole scene in PNG. - - Creates instance with family 'renderScene' which will have all layers - to render which will be composite into one result. The instance is not - collected from scene. - - Scene will be rendered with all visible layers similar way like review is. - - Instance is disabled if there are any created instances of 'renderLayer' - or 'renderPass'. That is because it is expected that this instance is - used as lazy publish of TVPaint file. - - Subset name is created similar way like 'renderLayer' family. It can use - `renderPass` and `renderLayer` keys which can be set using settings and - `variant` is filled using `renderPass` value. - """ - label = "Collect Render Scene" - order = pyblish.api.CollectorOrder - 0.39 - hosts = ["tvpaint"] - - # Value of 'render_pass' in subset name template - render_pass = "beauty" - - # Settings attributes - enabled = False - # Value of 'render_layer' and 'variant' in subset name template - render_layer = "Main" - - def process(self, context): - # Check if there are created instances of renderPass and renderLayer - # - that will define if renderScene instance is enabled after - # collection - any_created_instance = False - for instance in context: - family = instance.data["family"] - if family in ("renderPass", "renderLayer"): - any_created_instance = True - break - - # Global instance data modifications - # Fill families - family = "renderScene" - # Add `review` family for thumbnail integration - families = [family, "review"] - - # Collect asset doc to get asset id - # - not sure if it's good idea to require asset id in - # get_subset_name? - workfile_context = context.data["workfile_context"] - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] - asset_name = workfile_context["asset"] - asset_doc = get_asset_by_name(project_name, asset_name) - - # Host name from environment variable - host_name = context.data["hostName"] - # Variant is using render pass name - variant = self.render_layer - dynamic_data = { - "renderlayer": self.render_layer, - "renderpass": self.render_pass, - } - # TODO remove - Backwards compatibility for old subset name templates - # - added 2022/04/28 - dynamic_data["render_layer"] = dynamic_data["renderlayer"] - dynamic_data["render_pass"] = dynamic_data["renderpass"] - - task_name = workfile_context["task"] - subset_name = get_subset_name( - "render", - variant, - task_name, - asset_doc, - project_name, - host_name, - dynamic_data=dynamic_data, - project_settings=context.data["project_settings"] - ) - - instance_data = { - "family": family, - "families": families, - "fps": context.data["sceneFps"], - "subset": subset_name, - "name": subset_name, - "label": "{} [{}-{}]".format( - subset_name, - context.data["sceneMarkIn"] + 1, - context.data["sceneMarkOut"] + 1 - ), - "active": not any_created_instance, - "publish": not any_created_instance, - "representations": [], - "layers": copy.deepcopy(context.data["layersData"]), - "asset": asset_name, - "task": task_name, - # Add render layer to instance data - "renderlayer": self.render_layer - } - - instance = context.create_instance(**instance_data) - - self.log.debug("Created instance: {}\n{}".format( - instance, json.dumps(instance.data, indent=4) - )) From b21d55c10d9885c1d184e609253592bf4f44c6e7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:24:09 +0100 Subject: [PATCH 172/281] change families filter for validate layers visibility --- openpype/hosts/tvpaint/plugins/create/create_render.py | 2 +- .../hosts/tvpaint/plugins/publish/validate_layers_visibility.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 2b693d4bc0..2d44282879 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -602,4 +602,4 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): label="Review", default=self.mark_for_review ) - ] \ No newline at end of file + ] diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py index d3a04cc69f..47632453fc 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py @@ -8,7 +8,7 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin): label = "Validate Layers Visibility" order = pyblish.api.ValidatorOrder - families = ["review", "renderPass", "renderLayer", "renderScene"] + families = ["review", "render"] def process(self, instance): layer_names = set() From 8598d5c1f3f5d10c9c72ad4c0f413cb5119d70fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:25:18 +0100 Subject: [PATCH 173/281] remove empty lines --- openpype/hosts/tvpaint/plugins/create/create_render.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 2d44282879..7acd9b2260 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -579,8 +579,6 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): existing_instance["creator_attributes"]["render_pass_name"] ) - - def _get_label(self, subset_name, render_layer_name): return subset_name.format(**prepare_template_data({ "renderlayer": render_layer_name From e93c5d0d4055db7e2ff8cd7067fda24ccc243250 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:26:27 +0100 Subject: [PATCH 174/281] OP-4928 - fix wrong usage of legacy_io Import was removed, but usage stayed. Now it should be replaced from context --- openpype/hosts/photoshop/plugins/create/create_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index cdea82cb05..3d82d6b6f0 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -193,7 +193,7 @@ class ImageCreator(Creator): instance_data.pop("uuid") if not instance_data.get("task"): - instance_data["task"] = legacy_io.Session.get("AVALON_TASK") + instance_data["task"] = self.create_context.get_current_task_name() if not instance_data.get("variant"): instance_data["variant"] = '' From 423f2bcbdadc723d6499f8f92a9ed391533a75e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Feb 2023 12:26:50 +0100 Subject: [PATCH 175/281] removing python3 only code --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 3216c596da..55a1346b08 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -54,6 +54,6 @@ def create_custom_tempdir(project_name, anatomy=None): os.makedirs(custom_tempdir) except IOError as error: raise IOError( - "Path couldn't be created: {}".format(error)) from error + "Path couldn't be created: {}".format(error)) return custom_tempdir From 410ea87e18a582628fbd456549207e2dac2ef164 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:27:44 +0100 Subject: [PATCH 176/281] OP-4928 - fix wrong usage of legacy_io Import should be removed. Now it should be replaced from context. --- openpype/hosts/aftereffects/plugins/create/create_render.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 10ded8b912..02f045b0ec 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -6,8 +6,7 @@ from openpype.hosts.aftereffects import api from openpype.pipeline import ( Creator, CreatedInstance, - CreatorError, - legacy_io, + CreatorError ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances from openpype.lib import prepare_template_data @@ -195,7 +194,7 @@ class RenderCreator(Creator): instance_data.pop("uuid") if not instance_data.get("task"): - instance_data["task"] = legacy_io.Session.get("AVALON_TASK") + instance_data["task"] = self.create_context.get_current_task_name() if not instance_data.get("creator_attributes"): is_old_farm = instance_data["family"] != "renderLocal" From eb5d1e3816b07760c6ffdc8c71999fe1167dfdf9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:29:52 +0100 Subject: [PATCH 177/281] resave to remove empty line --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index d475c337d9..2999d1427d 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -533,4 +533,4 @@ "profiles": [] }, "filters": {} -} +} \ No newline at end of file From 66c42dde73174c8a3b288419a616f8c23b98064a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:32:41 +0100 Subject: [PATCH 178/281] OP-4928 - removed legacy_io in workfile creator in PS Legacy_io should be eradicated, replaced by abstracted methods --- .../photoshop/plugins/create/workfile_creator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index 8ee9a0d832..f5d56adcbc 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -2,8 +2,7 @@ import openpype.hosts.photoshop.api as api from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, - CreatedInstance, - legacy_io + CreatedInstance ) from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances @@ -38,10 +37,11 @@ class PSWorkfileCreator(AutoCreator): existing_instance = instance break - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] + context = self.create_context + project_name = context.get_current_project_name() + asset_name = context.get_current_asset_name() + task_name = context.get_current_task_name() + host_name = context.host_name if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( From 03013095023cdce494142740a70efdbce60cb03c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:33:35 +0100 Subject: [PATCH 179/281] OP-4928 - removed legacy_io in workfile creator in AE Legacy_io should be eradicated, replaced by abstracted methods --- .../aftereffects/plugins/create/workfile_creator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index c698af896b..2e7b9d4a7e 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -2,8 +2,7 @@ import openpype.hosts.aftereffects.api as api from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, - CreatedInstance, - legacy_io, + CreatedInstance ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances @@ -38,10 +37,11 @@ class AEWorkfileCreator(AutoCreator): existing_instance = instance break - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] + context = self.create_context + project_name = context.get_current_project_name() + asset_name = context.get_current_asset_name() + task_name = context.get_current_task_name() + host_name = context.host_name if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) From 122a72506257bf15354b0dedf20fdd73495c8c47 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:17:25 +0100 Subject: [PATCH 180/281] implemented basic of conver plugin --- .../tvpaint/plugins/create/convert_legacy.py | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 openpype/hosts/tvpaint/plugins/create/convert_legacy.py diff --git a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py new file mode 100644 index 0000000000..79244b4fc4 --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py @@ -0,0 +1,135 @@ +import collections + +from openpype.pipeline.create.creator_plugins import ( + SubsetConvertorPlugin, + cache_and_get_instances, +) +from openpype.hosts.tvpaint.api.plugin import SHARED_DATA_KEY +from openpype.hosts.tvpaint.api.lib import get_groups_data + + +class TVPaintLegacyConverted(SubsetConvertorPlugin): + identifier = "tvpaint.legacy.converter" + + def find_instances(self): + instances = cache_and_get_instances( + self, SHARED_DATA_KEY, self.host.list_instances + ) + + for instance in instances: + if instance.get("creator_identifier") is None: + self.add_convertor_item("Convert legacy instances") + return + + def convert(self): + current_instances = self.host.list_instances() + to_convert = collections.defaultdict(list) + converted = False + for instance in current_instances: + if instance.get("creator_identifier") is not None: + continue + converted = True + + family = instance.get("family") + if family in ( + "renderLayer", + "renderPass", + "renderScene", + "review", + "workfile", + ): + to_convert[family].append(instance) + else: + instance["keep"] = False + + # Skip if nothing was changed + if not converted: + self.remove_convertor_item() + return + + self._convert_render_layers( + to_convert["renderLayer"], current_instances) + self._convert_render_passes( + to_convert["renderpass"], current_instances) + self._convert_render_scenes( + to_convert["renderScene"], current_instances) + self._convert_workfiles( + to_convert["workfile"], current_instances) + self._convert_reviews( + to_convert["review"], current_instances) + + new_instances = [ + instance + for instance in current_instances + if instance.get("keep") is not False + ] + self.host.write_instances(new_instances) + # remove legacy item if all is fine + self.remove_convertor_item() + + def _convert_render_layers(self, render_layers, current_instances): + if not render_layers: + return + + render_layers_by_group_id = {} + for instance in current_instances: + if instance.get("creator_identifier") == "render.layer": + group_id = instance["creator_identifier"]["group_id"] + render_layers_by_group_id[group_id] = instance + + groups_by_id = { + group["group_id"]: group + for group in get_groups_data() + } + for render_layer in render_layers: + group_id = render_layer.pop("group_id") + if group_id in render_layers_by_group_id: + render_layer["keep"] = False + continue + render_layer["creator_identifier"] = "render.layer" + render_layer["instance_id"] = render_layer.pop("uuid") + render_layer["creator_attributes"] = { + "group_id": group_id + } + render_layer["family"] = "render" + group = groups_by_id[group_id] + group["variant"] = group["name"] + + def _convert_render_passes(self, render_passes, current_instances): + if not render_passes: + return + + render_layers_by_group_id = {} + for instance in current_instances: + if instance.get("creator_identifier") == "render.layer": + group_id = instance["creator_identifier"]["group_id"] + render_layers_by_group_id[group_id] = instance + + for render_pass in render_passes: + group_id = render_pass.pop("group_id") + render_layer = render_layers_by_group_id.get(group_id) + if not render_layer: + render_pass["keep"] = False + continue + + render_pass["creator_identifier"] = "render.pass" + render_pass["instance_id"] = render_pass.pop("uuid") + render_pass["family"] = "render" + + render_pass["creator_attributes"] = { + "render_layer_instance_id": render_layer["instance_id"] + } + render_pass["variant"] = render_pass.pop("pass") + render_pass.pop("renderlayer") + + def _convert_render_scenes(self, render_scenes, current_instances): + for render_scene in render_scenes: + render_scene["keep"] = False + + def _convert_workfiles(self, workfiles, current_instances): + for render_scene in workfiles: + render_scene["keep"] = False + + def _convert_reviews(self, reviews, current_instances): + for render_scene in reviews: + render_scene["keep"] = False From 06e11a45c20740307a45273ee236d44f3272d55c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:17:39 +0100 Subject: [PATCH 181/281] removed legacy creator --- openpype/hosts/tvpaint/api/plugin.py | 47 ++-------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index 397e4295f5..64784bfb83 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -1,21 +1,15 @@ import re -import uuid -from openpype.pipeline import ( - LegacyCreator, - LoaderPlugin, - registered_host, -) +from openpype.pipeline import LoaderPlugin from openpype.pipeline.create import ( CreatedInstance, get_subset_name, AutoCreator, - Creator as NewCreator, + Creator, ) from openpype.pipeline.create.creator_plugins import cache_and_get_instances from .lib import get_layers_data -from .pipeline import get_current_workfile_context SHARED_DATA_KEY = "openpype.tvpaint.instances" @@ -86,7 +80,7 @@ class TVPaintCreatorCommon: ) -class TVPaintCreator(NewCreator, TVPaintCreatorCommon): +class TVPaintCreator(Creator, TVPaintCreatorCommon): def collect_instances(self): self._collect_create_instances() @@ -147,41 +141,6 @@ class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon): return self._custom_get_subset_name(*args, **kwargs) -class Creator(LegacyCreator): - def __init__(self, *args, **kwargs): - super(Creator, self).__init__(*args, **kwargs) - # Add unified identifier created with `uuid` module - self.data["uuid"] = str(uuid.uuid4()) - - @classmethod - def get_dynamic_data(cls, *args, **kwargs): - dynamic_data = super(Creator, cls).get_dynamic_data(*args, **kwargs) - - # Change asset and name by current workfile context - workfile_context = get_current_workfile_context() - asset_name = workfile_context.get("asset") - task_name = workfile_context.get("task") - if "asset" not in dynamic_data and asset_name: - dynamic_data["asset"] = asset_name - - if "task" not in dynamic_data and task_name: - dynamic_data["task"] = task_name - return dynamic_data - - def write_instances(self, data): - self.log.debug( - "Storing instance data to workfile. {}".format(str(data)) - ) - host = registered_host() - return host.write_instances(data) - - def process(self): - host = registered_host() - data = host.list_instances() - data.append(self.data) - self.write_instances(data) - - class Loader(LoaderPlugin): hosts = ["tvpaint"] From 3b87087a574fc35aa8cb7c7d190f1a5f8291f5c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:39:51 +0100 Subject: [PATCH 182/281] don't use project document during context settings --- openpype/hosts/tvpaint/api/pipeline.py | 55 +++++--------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 3794bf2e24..88ad3b4b4d 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -147,27 +147,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def write_instances(self, data): return write_instances(data) - # --- Legacy Create --- - def remove_instance(self, instance): - """Remove instance from current workfile metadata. - - Implementation for Subset manager tool. - """ - - current_instances = get_workfile_metadata(SECTION_NAME_INSTANCES) - instance_id = instance.get("uuid") - found_idx = None - if instance_id: - for idx, _inst in enumerate(current_instances): - if _inst["uuid"] == instance_id: - found_idx = idx - break - - if found_idx is None: - return - current_instances.pop(found_idx) - write_instances(current_instances) - # --- Workfile --- def open_workfile(self, filepath): george_script = "tv_LoadProject '\"'\"{}\"'\"'".format( @@ -515,17 +494,19 @@ def set_context_settings(asset_doc=None): Change fps, resolution and frame start/end. """ - project_name = legacy_io.active_project() - if asset_doc is None: - asset_name = legacy_io.Session["AVALON_ASSET"] - # Use current session asset if not passed - asset_doc = get_asset_by_name(project_name, asset_name) + width_key = "resolutionWidth" + height_key = "resolutionHeight" - project_doc = get_project(project_name) + width = asset_doc["data"].get(width_key) + height = asset_doc["data"].get(height_key) + if width is None or height is None: + print("Resolution was not found!") + else: + execute_george( + "tv_resizepage {} {} 0".format(width, height) + ) framerate = asset_doc["data"].get("fps") - if framerate is None: - framerate = project_doc["data"].get("fps") if framerate is not None: execute_george( @@ -534,22 +515,6 @@ def set_context_settings(asset_doc=None): else: print("Framerate was not found!") - width_key = "resolutionWidth" - height_key = "resolutionHeight" - - width = asset_doc["data"].get(width_key) - height = asset_doc["data"].get(height_key) - if width is None or height is None: - width = project_doc["data"].get(width_key) - height = project_doc["data"].get(height_key) - - if width is None or height is None: - print("Resolution was not found!") - else: - execute_george( - "tv_resizepage {} {} 0".format(width, height) - ) - frame_start = asset_doc["data"].get("frameStart") frame_end = asset_doc["data"].get("frameEnd") From 7438aebc58217d85327360ee228e469dd138b168 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:40:16 +0100 Subject: [PATCH 183/281] remove logic related to pyblish instance toggle --- openpype/hosts/tvpaint/api/pipeline.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 88ad3b4b4d..4a737f8f72 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -87,10 +87,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): registered_callbacks = ( pyblish.api.registered_callbacks().get("instanceToggled") or [] ) - if self.on_instance_toggle not in registered_callbacks: - pyblish.api.register_callback( - "instanceToggled", self.on_instance_toggle - ) register_event_callback("application.launched", self.initial_launch) register_event_callback("application.exit", self.application_exit) @@ -209,28 +205,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rest_api_url = "{}/timers_manager/stop_timer".format(webserver_url) requests.post(rest_api_url) - # --- Legacy Publish --- - def on_instance_toggle(self, instance, old_value, new_value): - """Update instance data in workfile on publish toggle.""" - # Review may not have real instance in wokrfile metadata - if not instance.data.get("uuid"): - return - - instance_id = instance.data["uuid"] - found_idx = None - current_instances = list_instances() - for idx, workfile_instance in enumerate(current_instances): - if workfile_instance.get("uuid") == instance_id: - found_idx = idx - break - - if found_idx is None: - return - - if "active" in current_instances[found_idx]: - current_instances[found_idx]["active"] = new_value - self.write_instances(current_instances) - def containerise( name, namespace, members, context, loader, current_containers=None From 7721520dd8cbb58b96a39cc2bb46ce0034767db2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:40:36 +0100 Subject: [PATCH 184/281] set context settings is called with explicit arguments --- openpype/hosts/tvpaint/api/pipeline.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 4a737f8f72..575e6aa755 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -185,7 +185,15 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return log.info("Setting up project...") - set_context_settings() + global_context = get_global_context() + project_name = global_context.get("project_name") + asset_name = global_context.get("aset_name") + if not project_name or not asset_name: + return + + asset_doc = get_asset_by_name(project_name, asset_name) + + set_context_settings(project_name, asset_doc) def application_exit(self): """Logic related to TimerManager. @@ -462,7 +470,7 @@ def get_containers(): return output -def set_context_settings(asset_doc=None): +def set_context_settings(project_name, asset_doc): """Set workfile settings by asset document data. Change fps, resolution and frame start/end. From 118cf08d91e9fd3a4a540a5bf5d118198965fad6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:40:50 +0100 Subject: [PATCH 185/281] removed not needed tools --- .../hosts/tvpaint/api/communication_server.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index 6fd2d69373..e94e64e04a 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -309,8 +309,6 @@ class QtTVPaintRpc(BaseTVPaintRpc): self.add_methods( (route_name, self.workfiles_tool), (route_name, self.loader_tool), - (route_name, self.creator_tool), - (route_name, self.subset_manager_tool), (route_name, self.publish_tool), (route_name, self.scene_inventory_tool), (route_name, self.library_loader_tool), @@ -330,18 +328,6 @@ class QtTVPaintRpc(BaseTVPaintRpc): self._execute_in_main_thread(item) return - async def creator_tool(self): - log.info("Triggering Creator tool") - item = MainThreadItem(self.tools_helper.show_creator) - await self._async_execute_in_main_thread(item, wait=False) - - async def subset_manager_tool(self): - log.info("Triggering Subset Manager tool") - item = MainThreadItem(self.tools_helper.show_subset_manager) - # Do not wait for result of callback - self._execute_in_main_thread(item, wait=False) - return - async def publish_tool(self): log.info("Triggering Publish tool") item = MainThreadItem(self.tools_helper.show_publisher_tool) @@ -859,10 +845,6 @@ class QtCommunicator(BaseCommunicator): "callback": "loader_tool", "label": "Load", "help": "Open loader tool" - }, { - "callback": "creator_tool", - "label": "Create", - "help": "Open creator tool" }, { "callback": "scene_inventory_tool", "label": "Scene inventory", From 38ff3adc4ea9e614f44f7fbbb84655bb91b87250 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:42:17 +0100 Subject: [PATCH 186/281] use context from create context --- openpype/hosts/tvpaint/api/plugin.py | 8 +++----- .../tvpaint/plugins/create/create_render.py | 20 +++++++++---------- .../tvpaint/plugins/create/create_review.py | 10 +++++----- .../tvpaint/plugins/create/create_workfile.py | 10 +++++----- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index 64784bfb83..96b99199f2 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -31,7 +31,6 @@ class TVPaintCreatorCommon: instance = CreatedInstance.from_existing(instance_data, self) self._add_instance_to_context(instance) - def _update_create_instances(self, update_list): if not update_list: return @@ -109,10 +108,9 @@ class TVPaintCreator(Creator, TVPaintCreatorCommon): def get_dynamic_data(self, *args, **kwargs): # Change asset and name by current workfile context - # TODO use context from 'create_context' - workfile_context = self.host.get_current_context() - asset_name = workfile_context.get("asset") - task_name = workfile_context.get("task") + create_context = self.create_context + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() output = {} if asset_name: output["asset"] = asset_name diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 7acd9b2260..0df066edc4 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -504,11 +504,11 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): return dynamic_data def _create_new_instance(self): - context = self.host.get_current_context() - host_name = self.host.name - project_name = context["project_name"] - asset_name = context["asset_name"] - task_name = context["task_name"] + create_context = self.create_context + host_name = create_context.host_name + project_name = create_context.get_current_project_name() + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( @@ -551,11 +551,11 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): if existing_instance is None: return self._create_new_instance() - context = self.host.get_current_context() - host_name = self.host.name - project_name = context["project_name"] - asset_name = context["asset_name"] - task_name = context["task_name"] + create_context = self.create_context + host_name = create_context.host_name + project_name = create_context.get_current_project_name() + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() if ( existing_instance["asset"] != asset_name diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 84127eb9b7..1c220831bf 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -17,11 +17,11 @@ class TVPaintReviewCreator(TVPaintAutoCreator): existing_instance = instance break - context = self.host.get_current_context() - host_name = self.host.name - project_name = context["project_name"] - asset_name = context["asset_name"] - task_name = context["task_name"] + create_context = self.create_context + host_name = create_context.host_name + project_name = create_context.get_current_project_name() + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index e421fbc3f8..d968e4f77d 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -17,11 +17,11 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): existing_instance = instance break - context = self.host.get_current_context() - host_name = self.host.name - project_name = context["project_name"] - asset_name = context["asset_name"] - task_name = context["task_name"] + create_context = self.create_context + host_name = create_context.host_name + project_name = create_context.get_current_project_name() + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) From 6ab581df7da24302158d32c9a68a9baca33b1cb3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:45:22 +0100 Subject: [PATCH 187/281] on first reset always go to create tab --- openpype/tools/publisher/window.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 097e289f32..5ef25c9f8c 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -647,10 +647,7 @@ class PublisherWindow(QtWidgets.QDialog): # otherwise 'create' is used # - this happens only on first show if first_reset: - if self._overview_widget.has_items(): - self._go_to_publish_tab() - else: - self._go_to_create_tab() + self._go_to_create_tab() elif ( not self._is_on_create_tab() From 1cc9a7a90fd6deef343b45f0944bcefed6497521 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:45:45 +0100 Subject: [PATCH 188/281] change tab on reset only if is on report tab (Details for user) --- openpype/tools/publisher/window.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5ef25c9f8c..ef9c99d998 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -649,11 +649,8 @@ class PublisherWindow(QtWidgets.QDialog): if first_reset: self._go_to_create_tab() - elif ( - not self._is_on_create_tab() - and not self._is_on_publish_tab() - ): - # If current tab is not 'Create' or 'Publish' go to 'Publish' + elif self._is_on_report_tab(): + # Go to 'Publish' tab if is on 'Details' tab # - this can happen when publishing started and was reset # at that moment it doesn't make sense to stay at publish # specific tabs. From 37a7841db8024341cbc4fa0c7881c6925ab7a188 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:46:02 +0100 Subject: [PATCH 189/281] reordered methods to match order of tabs in UI --- openpype/tools/publisher/window.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index ef9c99d998..86eed31afd 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -566,24 +566,24 @@ class PublisherWindow(QtWidgets.QDialog): def _go_to_publish_tab(self): self._set_current_tab("publish") - def _go_to_details_tab(self): - self._set_current_tab("details") - def _go_to_report_tab(self): self._set_current_tab("report") + def _go_to_details_tab(self): + self._set_current_tab("details") + def _is_on_create_tab(self): return self._is_current_tab("create") def _is_on_publish_tab(self): return self._is_current_tab("publish") - def _is_on_details_tab(self): - return self._is_current_tab("details") - def _is_on_report_tab(self): return self._is_current_tab("report") + def _is_on_details_tab(self): + return self._is_current_tab("details") + def _set_publish_overlay_visibility(self, visible): if visible: widget = self._publish_overlay From 0182f73e32f42f130bf71249ce137d1b95788953 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 16:51:23 +0100 Subject: [PATCH 190/281] fix double spaces --- openpype/hosts/tvpaint/plugins/create/create_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 0df066edc4..2069a657b9 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -228,7 +228,7 @@ class CreateRenderlayer(TVPaintCreator): render_layer_instances = {} render_pass_instances = collections.defaultdict(list) - for instance in self.create_context.instances: + for instance in self.create_context.instances: if instance.creator_identifier == CreateRenderPass.identifier: render_layer_id = ( instance["creator_attributes"]["render_layer_instance_id"] From 7397bdcac23619177998c8460a9297af5d135319 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 17:52:42 +0100 Subject: [PATCH 191/281] added host property to legacy convertor --- openpype/pipeline/create/creator_plugins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 53acb618ed..14c5d70462 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -79,6 +79,10 @@ class SubsetConvertorPlugin(object): self._log = Logger.get_logger(self.__class__.__name__) return self._log + @property + def host(self): + return self._create_context.host + @abstractproperty def identifier(self): """Converted identifier. From bb112ad1560294c92285b8794dedf28dad5c72cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 17:53:01 +0100 Subject: [PATCH 192/281] fix legacy convertor --- openpype/hosts/tvpaint/plugins/create/convert_legacy.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py index 79244b4fc4..215c87f3e5 100644 --- a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py +++ b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py @@ -12,14 +12,11 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): identifier = "tvpaint.legacy.converter" def find_instances(self): - instances = cache_and_get_instances( + instances_by_identifier = cache_and_get_instances( self, SHARED_DATA_KEY, self.host.list_instances ) - - for instance in instances: - if instance.get("creator_identifier") is None: - self.add_convertor_item("Convert legacy instances") - return + if instances_by_identifier[None]: + self.add_convertor_item("Convert legacy instances") def convert(self): current_instances = self.host.list_instances() From c0f9811808c6a60f221c1b80a3c3e6bd76d4a6ac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 17:53:14 +0100 Subject: [PATCH 193/281] fix render scene subset name creation --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 2069a657b9..d2eb693ab9 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -579,9 +579,9 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): existing_instance["creator_attributes"]["render_pass_name"] ) - def _get_label(self, subset_name, render_layer_name): + def _get_label(self, subset_name, render_pass_name): return subset_name.format(**prepare_template_data({ - "renderlayer": render_layer_name + "renderpass": render_pass_name })) def get_instance_attr_defs(self): From 54be749de62cbc5ee2847a71d6ddb4a02c7ff04c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 17:55:25 +0100 Subject: [PATCH 194/281] safe string formatting --- .../tvpaint/plugins/create/create_render.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d2eb693ab9..ebce695801 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -389,11 +389,16 @@ class CreateRenderPass(TVPaintCreator): subset_name_fill_data = {"renderlayer": render_layer} # Format dynamic keys in subset name - new_subset_name = subset_name.format( - **prepare_template_data(subset_name_fill_data) - ) - self.log.info(f"New subset name is \"{new_subset_name}\".") - instance_data["label"] = new_subset_name + label = subset_name + try: + label = label.format( + **prepare_template_data(subset_name_fill_data) + ) + except (KeyError, ValueError): + pass + + self.log.info(f"New subset name is \"{label}\".") + instance_data["label"] = label instance_data["group"] = f"{self.get_group_label()} ({render_layer})" instance_data["layer_names"] = list(selected_layer_names) if "creator_attributes" not in instance_data: @@ -580,9 +585,14 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): ) def _get_label(self, subset_name, render_pass_name): - return subset_name.format(**prepare_template_data({ - "renderpass": render_pass_name - })) + try: + subset_name = subset_name.format(**prepare_template_data({ + "renderpass": render_pass_name + })) + except (KeyError, ValueError): + pass + + return subset_name def get_instance_attr_defs(self): return [ From 8441a5c54b64b14f1d3cd1911c89be36c7f46f3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:14:47 +0100 Subject: [PATCH 195/281] added at least some icons to creators --- openpype/hosts/tvpaint/plugins/create/create_render.py | 5 +++-- openpype/hosts/tvpaint/plugins/create/create_review.py | 1 + openpype/hosts/tvpaint/plugins/create/create_workfile.py | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index ebce695801..e8d6d2bb88 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -61,7 +61,7 @@ class CreateRenderlayer(TVPaintCreator): family = "render" subset_template_family_filter = "renderLayer" identifier = "render.layer" - icon = "fa.cube" + icon = "fa5.images" # George script to change color group rename_script_template = ( @@ -266,11 +266,11 @@ class CreateRenderlayer(TVPaintCreator): class CreateRenderPass(TVPaintCreator): - icon = "fa.cube" family = "render" subset_template_family_filter = "renderPass" identifier = "render.pass" label = "Render Pass" + icon = "fa5.image" order = CreateRenderlayer.order + 10 @@ -496,6 +496,7 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): subset_template_family_filter = "renderScene" identifier = "render.scene" label = "Scene Render" + icon = "fa.file-image-o" # Settings default_variant = "Main" diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 1c220831bf..1172b53032 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -7,6 +7,7 @@ class TVPaintReviewCreator(TVPaintAutoCreator): family = "review" identifier = "scene.review" label = "Review" + icon = "ei.video" default_variant = "Main" diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index d968e4f77d..7e8978e73a 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -7,6 +7,7 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): family = "workfile" identifier = "workfile" label = "Workfile" + icon = "fa.file-o" default_variant = "Main" From 7d7c8a8d74ef201e4e063a6454ca8b15ac6483dc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:26:31 +0100 Subject: [PATCH 196/281] added some dosctrings, comments and descriptions --- .../tvpaint/plugins/create/convert_legacy.py | 18 ++++++++ .../tvpaint/plugins/create/create_render.py | 46 ++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py index 215c87f3e5..538c6e4c5e 100644 --- a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py +++ b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py @@ -9,6 +9,14 @@ from openpype.hosts.tvpaint.api.lib import get_groups_data class TVPaintLegacyConverted(SubsetConvertorPlugin): + """Conversion of legacy instances in scene to new creators. + + This convertor handles only instances created by core creators. + + All instances that would be created using auto-creators are removed as at + the moment of finding them would there already be existing instances. + """ + identifier = "tvpaint.legacy.converter" def find_instances(self): @@ -68,6 +76,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): if not render_layers: return + # Look for possible existing render layers in scene render_layers_by_group_id = {} for instance in current_instances: if instance.get("creator_identifier") == "render.layer": @@ -80,22 +89,30 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): } for render_layer in render_layers: group_id = render_layer.pop("group_id") + # Just remove legacy instance if group is already occupied if group_id in render_layers_by_group_id: render_layer["keep"] = False continue + # Add identifier render_layer["creator_identifier"] = "render.layer" + # Change 'uuid' to 'instance_id' render_layer["instance_id"] = render_layer.pop("uuid") + # Fill creator attributes render_layer["creator_attributes"] = { "group_id": group_id } render_layer["family"] = "render" group = groups_by_id[group_id] + # Use group name for variant group["variant"] = group["name"] def _convert_render_passes(self, render_passes, current_instances): if not render_passes: return + # Render passes must have available render layers so we look for render + # layers first + # - '_convert_render_layers' must be called before this method render_layers_by_group_id = {} for instance in current_instances: if instance.get("creator_identifier") == "render.layer": @@ -119,6 +136,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): render_pass["variant"] = render_pass.pop("pass") render_pass.pop("renderlayer") + # Rest of instances are just marked for deletion def _convert_render_scenes(self, render_scenes, current_instances): for render_scene in render_scenes: render_scene["keep"] = False diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index e8d6d2bb88..87d9014922 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -25,6 +25,10 @@ default 'color' blend more. In that case it is not recommended to use this workflow at all as other blend modes may affect all layers in clip which can't be done. +There is special case for simple publishing of scene which is called +'render.scene'. That will use all visible layers and render them as one big +sequence. + Todos: Add option to extract marked layers and passes as json output format for AfterEffects. @@ -53,9 +57,42 @@ from openpype.hosts.tvpaint.api.lib import ( execute_george_through_file, ) +RENDER_LAYER_DETAILED_DESCRIPTIONS = ( +"""Render Layer is "a group of TVPaint layers" + +Be aware Render Layer is not TVPaint layer. + +All TVPaint layers in the scene with the color group id are rendered in the +beauty pass. To create sub passes use Render Layer creator which is +dependent on existence of render layer instance. + +The group can represent an asset (tree) or different part of scene that consist +of one or more TVPaint layers that can be used as single item during +compositing (for example). + +In some cases may be needed to have sub parts of the layer. For example 'Bob' +could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. +""" +) + + +RENDER_PASS_DETAILED_DESCRIPTIONS = ( +"""Render Pass is sub part of Rende Layer. + +Render Pass can consist of one or more TVPaint layers. Render Layers must +belong to a Render Layer. Marker TVPaint layers will change it's group color +to match group color of Render Layer. +""" +) + class CreateRenderlayer(TVPaintCreator): - """Mark layer group as one instance.""" + """Mark layer group as Render layer instance. + + All TVPaint layers in the scene with the color group id are rendered in the + beauty pass. To create sub passes use Render Layer creator which is + dependent on existence of render layer instance. + """ label = "Render Layer" family = "render" @@ -68,10 +105,15 @@ class CreateRenderlayer(TVPaintCreator): "tv_layercolor \"setcolor\"" " {clip_id} {group_id} {r} {g} {b} \"{name}\"" ) + # Order to be executed before Render Pass creator order = 90 + description = "Mark TVPaint color group as one Render Layer." + detailed_description = RENDER_LAYER_DETAILED_DESCRIPTIONS # Settings + # - Default render pass name for beauty render_pass = "beauty" + # - Mark by default instance for review mark_for_review = True def get_dynamic_data( @@ -271,6 +313,8 @@ class CreateRenderPass(TVPaintCreator): identifier = "render.pass" label = "Render Pass" icon = "fa5.image" + description = "Mark selected TVPaint layers as pass of Render Layer." + detailed_description = RENDER_PASS_DETAILED_DESCRIPTIONS order = CreateRenderlayer.order + 10 From d440956b4ecee14b9d1ca8962f1c523112c6c9d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:26:43 +0100 Subject: [PATCH 197/281] fix of empty chars --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 87d9014922..21f6f86eb6 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -71,7 +71,7 @@ of one or more TVPaint layers that can be used as single item during compositing (for example). In some cases may be needed to have sub parts of the layer. For example 'Bob' -could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. +could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. """ ) @@ -81,7 +81,7 @@ RENDER_PASS_DETAILED_DESCRIPTIONS = ( Render Pass can consist of one or more TVPaint layers. Render Layers must belong to a Render Layer. Marker TVPaint layers will change it's group color -to match group color of Render Layer. +to match group color of Render Layer. """ ) From b70c6e4bfd433c1470efa1fde319834ec7068264 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 18:32:56 +0100 Subject: [PATCH 198/281] OP-4938 - fix obsolete access to instance change --- openpype/hosts/aftereffects/plugins/create/create_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 02f045b0ec..c20b0ec51b 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -126,7 +126,7 @@ class RenderCreator(Creator): subset_change = _changes.get("subset") if subset_change: api.get_stub().rename_item(created_inst.data["members"][0], - subset_change[1]) + subset_change.new_value) def remove_instances(self, instances): for instance in instances: From f73b84d6c36bdf31b48acd01292404400eee0196 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:40:33 +0100 Subject: [PATCH 199/281] added some basic options for settings --- openpype/hosts/tvpaint/plugins/create/create_render.py | 8 +++++--- openpype/hosts/tvpaint/plugins/create/create_workfile.py | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 21f6f86eb6..255f2605aa 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -112,7 +112,7 @@ class CreateRenderlayer(TVPaintCreator): # Settings # - Default render pass name for beauty - render_pass = "beauty" + default_pass_name = "beauty" # - Mark by default instance for review mark_for_review = True @@ -122,7 +122,7 @@ class CreateRenderlayer(TVPaintCreator): dynamic_data = super().get_dynamic_data( variant, task_name, asset_doc, project_name, host_name, instance ) - dynamic_data["renderpass"] = self.render_pass + dynamic_data["renderpass"] = self.default_pass_name dynamic_data["renderlayer"] = variant return dynamic_data @@ -543,9 +543,9 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): icon = "fa.file-image-o" # Settings - default_variant = "Main" default_pass_name = "beauty" mark_for_review = True + active_on_create = False def get_dynamic_data(self, variant, *args, **kwargs): dynamic_data = super().get_dynamic_data(variant, *args, **kwargs) @@ -581,6 +581,8 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): self.default_pass_name ) } + if not self.active_on_create: + data["active"] = False new_instance = CreatedInstance( self.family, subset_name, data, self diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index 7e8978e73a..152d29cf6f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -9,6 +9,8 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): label = "Workfile" icon = "fa.file-o" + # Settings + active_on_create = True default_variant = "Main" def create(self): @@ -38,6 +40,8 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): "task": task_name, "variant": self.default_variant } + if not self.active_on_create: + data["active"] = False new_instance = CreatedInstance( self.family, subset_name, data, self From 9bb6c606985aa936279633f4b5bef2f7af1cbb71 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:40:56 +0100 Subject: [PATCH 200/281] added settings for tvpaint creators --- .../defaults/project_settings/tvpaint.json | 32 ++++ .../schema_project_tvpaint.json | 171 ++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 5a3e1dc2df..0441b2da00 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -10,6 +10,38 @@ } }, "stop_timer_on_application_exit": false, + "create": { + "create_workfile": { + "enabled": true, + "default_variant": "Main", + "default_variants": [] + }, + "create_review": { + "enabled": true, + "active_on_create": true, + "default_variant": "Main", + "default_variants": [] + }, + "create_render_scene": { + "enabled": true, + "active_on_create": false, + "mark_for_review": true, + "default_pass_name": "beauty", + "default_variant": "Main", + "default_variants": [] + }, + "create_render_layer": { + "mark_for_review": true, + "render_pass_name": "beauty", + "default_variant": "Main", + "default_variants": [] + }, + "create_render_pass": { + "mark_for_review": true, + "default_variant": "Main", + "default_variants": [] + } + }, "publish": { "CollectRenderScene": { "enabled": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index db38c938dc..10f4b538f7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -27,6 +27,177 @@ "key": "stop_timer_on_application_exit", "label": "Stop timer on application exit" }, + { + "type": "dict", + "collapsible": true, + "key": "create", + "label": "Create plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "create_workfile", + "label": "Create Workfile", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "default_variant", + "label": "Default variant" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "create_review", + "label": "Create Review", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "active_on_create", + "label": "Active by default" + }, + { + "type": "text", + "key": "default_variant", + "label": "Default variant" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "create_render_scene", + "label": "Create Render Scene", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "active_on_create", + "label": "Active by default" + }, + { + "type": "boolean", + "key": "mark_for_review", + "label": "Review by default" + }, + { + "type": "text", + "key": "default_pass_name", + "label": "Default beauty pass" + }, + { + "type": "text", + "key": "default_variant", + "label": "Default variant" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "create_render_layer", + "label": "Create Render Layer", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "mark_for_review", + "label": "Review by default" + }, + { + "type": "text", + "key": "render_pass_name", + "label": "Default beauty pass" + }, + { + "type": "text", + "key": "default_variant", + "label": "Default variant" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "create_render_pass", + "label": "Create Render Pass", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "mark_for_review", + "label": "Review by default" + }, + { + "type": "text", + "key": "default_variant", + "label": "Default variant" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + } + ] + } + ] + }, { "type": "dict", "collapsible": true, From 51196290b2419d37b3c71ead5501d918a47c9211 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 18:46:48 +0100 Subject: [PATCH 201/281] apply settings in creators --- .../tvpaint/plugins/create/create_render.py | 27 +++++++++++++++++++ .../tvpaint/plugins/create/create_review.py | 5 +++- .../tvpaint/plugins/create/create_workfile.py | 7 ++++- .../defaults/project_settings/tvpaint.json | 2 +- .../schema_project_tvpaint.json | 2 +- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 255f2605aa..fa724fabe2 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -116,6 +116,15 @@ class CreateRenderlayer(TVPaintCreator): # - Mark by default instance for review mark_for_review = True + def apply_settings(self, project_settings, system_settings): + plugin_settings = ( + project_settings["tvpain"]["create"]["create_render_layer"] + ) + self.default_variant = plugin_settings["default_variant"] + self.default_variants = plugin_settings["default_variants"] + self.default_pass_name = plugin_settings["default_pass_name"] + self.mark_for_review = plugin_settings["mark_for_review"] + def get_dynamic_data( self, variant, task_name, asset_doc, project_name, host_name, instance ): @@ -321,6 +330,14 @@ class CreateRenderPass(TVPaintCreator): # Settings mark_for_review = True + def apply_settings(self, project_settings, system_settings): + plugin_settings = ( + project_settings["tvpain"]["create"]["create_render_pass"] + ) + self.default_variant = plugin_settings["default_variant"] + self.default_variants = plugin_settings["default_variants"] + self.mark_for_review = plugin_settings["mark_for_review"] + def collect_instances(self): instances_by_identifier = self._cache_and_get_instances() render_layers = { @@ -547,6 +564,16 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): mark_for_review = True active_on_create = False + def apply_settings(self, project_settings, system_settings): + plugin_settings = ( + project_settings["tvpain"]["create"]["create_render_scene"] + ) + self.default_variant = plugin_settings["default_variant"] + self.default_variants = plugin_settings["default_variants"] + self.mark_for_review = plugin_settings["mark_for_review"] + self.active_on_create = plugin_settings["active_on_create"] + self.default_pass_name = plugin_settings["default_pass_name"] + def get_dynamic_data(self, variant, *args, **kwargs): dynamic_data = super().get_dynamic_data(variant, *args, **kwargs) dynamic_data["renderpass"] = "{renderpass}" diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 1172b53032..a0af10f3be 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -9,7 +9,10 @@ class TVPaintReviewCreator(TVPaintAutoCreator): label = "Review" icon = "ei.video" - default_variant = "Main" + def apply_settings(self, project_settings, system_settings): + plugin_settings = project_settings["tvpain"]["create"]["create_review"] + self.default_variant = plugin_settings["default_variant"] + self.default_variants = plugin_settings["default_variants"] def create(self): existing_instance = None diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index 152d29cf6f..e247072e3b 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -11,7 +11,12 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): # Settings active_on_create = True - default_variant = "Main" + + def apply_settings(self, project_settings, system_settings): + plugin_settings = project_settings["tvpain"]["create"]["create_workfile"] + self.default_variant = plugin_settings["default_variant"] + self.default_variants = plugin_settings["default_variants"] + self.active_on_create = plugin_settings["active_on_create"] def create(self): existing_instance = None diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 0441b2da00..74a5af403c 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -32,7 +32,7 @@ }, "create_render_layer": { "mark_for_review": true, - "render_pass_name": "beauty", + "default_pass_name": "beauty", "default_variant": "Main", "default_variants": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 10f4b538f7..d09c666d50 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -151,7 +151,7 @@ }, { "type": "text", - "key": "render_pass_name", + "key": "default_pass_name", "label": "Default beauty pass" }, { From a651553fe6847af29c5ee4becc02304cf2ee08e3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:41:44 +0100 Subject: [PATCH 202/281] fix settings access --- openpype/hosts/tvpaint/plugins/create/create_render.py | 6 +++--- openpype/hosts/tvpaint/plugins/create/create_review.py | 4 +++- openpype/hosts/tvpaint/plugins/create/create_workfile.py | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index fa724fabe2..cadd045fbf 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -118,7 +118,7 @@ class CreateRenderlayer(TVPaintCreator): def apply_settings(self, project_settings, system_settings): plugin_settings = ( - project_settings["tvpain"]["create"]["create_render_layer"] + project_settings["tvpaint"]["create"]["create_render_layer"] ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] @@ -332,7 +332,7 @@ class CreateRenderPass(TVPaintCreator): def apply_settings(self, project_settings, system_settings): plugin_settings = ( - project_settings["tvpain"]["create"]["create_render_pass"] + project_settings["tvpaint"]["create"]["create_render_pass"] ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] @@ -566,7 +566,7 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator): def apply_settings(self, project_settings, system_settings): plugin_settings = ( - project_settings["tvpain"]["create"]["create_render_scene"] + project_settings["tvpaint"]["create"]["create_render_scene"] ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index a0af10f3be..423c3ab30f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -10,7 +10,9 @@ class TVPaintReviewCreator(TVPaintAutoCreator): icon = "ei.video" def apply_settings(self, project_settings, system_settings): - plugin_settings = project_settings["tvpain"]["create"]["create_review"] + plugin_settings = ( + project_settings["tvpaint"]["create"]["create_review"] + ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index e247072e3b..cc64936bdd 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -13,7 +13,9 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): active_on_create = True def apply_settings(self, project_settings, system_settings): - plugin_settings = project_settings["tvpain"]["create"]["create_workfile"] + plugin_settings = ( + project_settings["tvpaint"]["create"]["create_workfile"] + ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] self.active_on_create = plugin_settings["active_on_create"] From 69cc794ed1492e2c22c84ee8ad2499a23ecbf023 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:41:55 +0100 Subject: [PATCH 203/281] fix indentation --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index cadd045fbf..41288e5968 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -58,7 +58,7 @@ from openpype.hosts.tvpaint.api.lib import ( ) RENDER_LAYER_DETAILED_DESCRIPTIONS = ( -"""Render Layer is "a group of TVPaint layers" + """Render Layer is "a group of TVPaint layers" Be aware Render Layer is not TVPaint layer. @@ -77,7 +77,7 @@ could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. RENDER_PASS_DETAILED_DESCRIPTIONS = ( -"""Render Pass is sub part of Rende Layer. + """Render Pass is sub part of Render Layer. Render Pass can consist of one or more TVPaint layers. Render Layers must belong to a Render Layer. Marker TVPaint layers will change it's group color From eef8990101eef7e79ad34b7b8429c164b93e14c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:52:39 +0100 Subject: [PATCH 204/281] public 'discover' function can expect all possible arguments --- openpype/pipeline/plugin_discover.py | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/plugin_discover.py b/openpype/pipeline/plugin_discover.py index 7edd9ac290..e5257b801a 100644 --- a/openpype/pipeline/plugin_discover.py +++ b/openpype/pipeline/plugin_discover.py @@ -135,11 +135,12 @@ class PluginDiscoverContext(object): allow_duplicates (bool): Validate class name duplications. ignore_classes (list): List of classes that will be ignored and not added to result. + return_report (bool): Output will be full report if set to 'True'. Returns: - DiscoverResult: Object holding succesfully discovered plugins, - ignored plugins, plugins with missing abstract implementation - and duplicated plugin. + Union[DiscoverResult, list[Any]]: Object holding successfully + discovered plugins, ignored plugins, plugins with missing + abstract implementation and duplicated plugin. """ if not ignore_classes: @@ -268,9 +269,34 @@ class _GlobalDiscover: return cls._context -def discover(superclass, allow_duplicates=True): +def discover( + superclass, + allow_duplicates=True, + ignore_classes=None, + return_report=False +): + """Find and return subclasses of `superclass` + + Args: + superclass (type): Class which determines discovered subclasses. + allow_duplicates (bool): Validate class name duplications. + ignore_classes (list): List of classes that will be ignored + and not added to result. + return_report (bool): Output will be full report if set to 'True'. + + Returns: + Union[DiscoverResult, list[Any]]: Object holding successfully + discovered plugins, ignored plugins, plugins with missing + abstract implementation and duplicated plugin. + """ + context = _GlobalDiscover.get_context() - return context.discover(superclass, allow_duplicates) + return context.discover( + superclass, + allow_duplicates, + ignore_classes, + return_report + ) def get_last_discovered_plugins(superclass): From 542405775a36c08075dc118dc0801be312d0e5e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:53:15 +0100 Subject: [PATCH 205/281] discover creators and convertors can pass other arguments to 'discover' function --- openpype/pipeline/create/creator_plugins.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 53acb618ed..74e6cb289a 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -605,12 +605,12 @@ class AutoCreator(BaseCreator): pass -def discover_creator_plugins(): - return discover(BaseCreator) +def discover_creator_plugins(*args, **kwargs): + return discover(BaseCreator, *args, **kwargs) -def discover_convertor_plugins(): - return discover(SubsetConvertorPlugin) +def discover_convertor_plugins(*args, **kwargs): + return discover(SubsetConvertorPlugin, *args, **kwargs) def discover_legacy_creator_plugins(): From 86a9c77c1e970a78d08888af5326c19c0fd51aa3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:53:47 +0100 Subject: [PATCH 206/281] reuse 'DiscoverResult' from plugin discover --- openpype/pipeline/create/context.py | 4 ++-- openpype/pipeline/publish/__init__.py | 2 -- openpype/pipeline/publish/lib.py | 23 +---------------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 89eec52676..8b5da74bc7 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -21,6 +21,7 @@ from openpype.lib.attribute_definitions import ( ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io +from openpype.pipeline.plugin_discover import DiscoverResult from .creator_plugins import ( Creator, @@ -1620,8 +1621,7 @@ class CreateContext: from openpype.pipeline import OpenPypePyblishPluginMixin from openpype.pipeline.publish import ( - publish_plugins_discover, - DiscoverResult + publish_plugins_discover ) # Reset publish plugins diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index dc6fc0f97a..86f3bde0dc 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -25,7 +25,6 @@ from .publish_plugins import ( from .lib import ( get_publish_template_name, - DiscoverResult, publish_plugins_discover, load_help_content_from_plugin, load_help_content_from_filepath, @@ -68,7 +67,6 @@ __all__ = ( "get_publish_template_name", - "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", "load_help_content_from_filepath", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index d0a9396a42..50623e5110 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -21,6 +21,7 @@ from openpype.settings import ( from openpype.pipeline import ( tempdir ) +from openpype.pipeline.plugin_discover import DiscoverResult from .contants import ( DEFAULT_PUBLISH_TEMPLATE, @@ -202,28 +203,6 @@ def get_publish_template_name( return template or default_template -class DiscoverResult: - """Hold result of publish plugins discovery. - - Stores discovered plugins duplicated plugins and file paths which - crashed on execution of file. - """ - def __init__(self): - self.plugins = [] - self.crashed_file_paths = {} - self.duplicated_plugins = [] - - def __iter__(self): - for plugin in self.plugins: - yield plugin - - def __getitem__(self, item): - return self.plugins[item] - - def __setitem__(self, item, value): - self.plugins[item] = value - - class HelpContent: def __init__(self, title, description, detail=None): self.title = title From b3a86bdbf540c8e8f0df6c52cb0873f2b84b513b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:54:18 +0100 Subject: [PATCH 207/281] store reports of discovered plugins --- openpype/pipeline/create/context.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8b5da74bc7..5f1371befa 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1379,6 +1379,8 @@ class CreateContext: # Instances by their ID self._instances_by_id = {} + self.creator_discover_result = None + self.convertor_discover_result = None # Discovered creators self.creators = {} # Prepare categories of creators @@ -1666,7 +1668,9 @@ class CreateContext: creators = {} autocreators = {} manual_creators = {} - for creator_class in discover_creator_plugins(): + report = discover_creator_plugins(return_report=True) + self.creator_discover_result = report + for creator_class in report.plugins: if inspect.isabstract(creator_class): self.log.info( "Skipping abstract Creator {}".format(str(creator_class)) @@ -1711,7 +1715,9 @@ class CreateContext: def _reset_convertor_plugins(self): convertors_plugins = {} - for convertor_class in discover_convertor_plugins(): + report = discover_convertor_plugins(return_report=True) + self.convertor_discover_result = report + for convertor_class in report.plugins: if inspect.isabstract(convertor_class): self.log.info( "Skipping abstract Creator {}".format(str(convertor_class)) From 0dc73617a65dfc947c3c2715a6948928403c9f54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:54:44 +0100 Subject: [PATCH 208/281] use reports to store crashed files to publish report --- openpype/tools/publisher/control.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9ab37f2a3e..023a20ca5e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -169,6 +169,8 @@ class PublishReport: def __init__(self, controller): self.controller = controller + self._create_discover_result = None + self._convert_discover_result = None self._publish_discover_result = None self._plugin_data = [] self._plugin_data_with_plugin = [] @@ -181,6 +183,10 @@ class PublishReport: def reset(self, context, create_context): """Reset report and clear all data.""" + self._create_discover_result = create_context.creator_discover_result + self._convert_discover_result = ( + create_context.convertor_discover_result + ) self._publish_discover_result = create_context.publish_discover_result self._plugin_data = [] self._plugin_data_with_plugin = [] @@ -293,9 +299,19 @@ class PublishReport: if plugin not in self._stored_plugins: plugins_data.append(self._create_plugin_data_item(plugin)) - crashed_file_paths = {} + reports = [] + if self._create_discover_result is not None: + reports.append(self._create_discover_result) + + if self._convert_discover_result is not None: + reports.append(self._convert_discover_result) + if self._publish_discover_result is not None: - items = self._publish_discover_result.crashed_file_paths.items() + reports.append(self._publish_discover_result) + + crashed_file_paths = {} + for report in reports: + items = report.crashed_file_paths.items() for filepath, exc_info in items: crashed_file_paths[filepath] = "".join( traceback.format_exception(*exc_info) From 56470c47e23bcb337b48731252439c22e0300130 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:55:32 +0100 Subject: [PATCH 209/281] added Args to documentation --- openpype/pipeline/create/creator_plugins.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 74e6cb289a..628245faf2 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -153,6 +153,12 @@ class BaseCreator: Single object should be used for multiple instances instead of single instance per one creator object. Do not store temp data or mid-process data to `self` if it's not Plugin specific. + + Args: + project_settings (Dict[str, Any]): Project settings. + system_settings (Dict[str, Any]): System settings. + create_context (CreateContext): Context which initialized creator. + headless (bool): Running in headless mode. """ # Label shown in UI From 71adfb8b5044e38ffc0c5edcb39f0eeb54e2ed18 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:56:07 +0100 Subject: [PATCH 210/281] update instance label on refresh --- openpype/tools/publisher/widgets/card_view_widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 47f8ebb914..3fd5243ce9 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -385,6 +385,7 @@ class InstanceCardWidget(CardWidget): self._last_subset_name = None self._last_variant = None + self._last_label = None icon_widget = IconValuePixmapLabel(group_icon, self) icon_widget.setObjectName("FamilyIconLabel") @@ -462,14 +463,17 @@ class InstanceCardWidget(CardWidget): def _update_subset_name(self): variant = self.instance["variant"] subset_name = self.instance["subset"] + label = self.instance.label if ( variant == self._last_variant and subset_name == self._last_subset_name + and label == self._last_label ): return self._last_variant = variant self._last_subset_name = subset_name + self._last_label = label # Make `variant` bold label = html_escape(self.instance.label) found_parts = set(re.findall(variant, label, re.IGNORECASE)) From 7739c3a54f0b12e106d05f8742dfa63266bbde7f Mon Sep 17 00:00:00 2001 From: mre7a <68907585+mre7a@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:43:13 +0100 Subject: [PATCH 211/281] Update openpype/hosts/maya/api/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 56a53c070c..1e6094e996 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -2,7 +2,7 @@ import json from maya import cmds -from openpype.pipeline import registered_host, legacy_io +from openpype.pipeline import registered_host, get_current_asset_name from openpype.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, AbstractTemplateBuilder, From 035c888d9be86d546c49569b370dbe1264844fa7 Mon Sep 17 00:00:00 2001 From: mre7a <68907585+mre7a@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:43:55 +0100 Subject: [PATCH 212/281] Update openpype/hosts/maya/api/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/workfile_template_builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 1e6094e996..094f45221c 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -50,6 +50,7 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): return True # update imported sets information + asset_name = get_current_asset_name() for node in imported_sets: if not cmds.attributeQuery("id", node=node, exists=True): continue @@ -57,9 +58,9 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): continue if not cmds.attributeQuery("asset", node=node, exists=True): continue - asset = legacy_io.Session["AVALON_ASSET"] - cmds.setAttr("{}.asset".format(node), asset, type="string") + cmds.setAttr( + "{}.asset".format(node), asset_name, type="string") return True From c2b2dbb3f32aec8041b6cf6b1da08b3968d330ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 10:47:24 +0100 Subject: [PATCH 213/281] fix compatibility of QAction in Publisher --- openpype/tools/publisher/widgets/widgets.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 587bcb059d..8da3886419 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -250,21 +250,25 @@ class PublishReportBtn(PublishIconBtn): self._actions = [] def add_action(self, label, identifier): - action = QtWidgets.QAction(label) - action.setData(identifier) - action.triggered.connect( - functools.partial(self._on_action_trigger, action) + self._actions.append( + (label, identifier) ) - self._actions.append(action) - def _on_action_trigger(self, action): - identifier = action.data() + def _on_action_trigger(self, identifier): self.triggered.emit(identifier) def mouseReleaseEvent(self, event): super(PublishReportBtn, self).mouseReleaseEvent(event) menu = QtWidgets.QMenu(self) - menu.addActions(self._actions) + actions = [] + for item in self._actions: + label, identifier = item + action = QtWidgets.QAction(label, menu) + action.triggered.connect( + functools.partial(self._on_action_trigger, identifier) + ) + actions.append(action) + menu.addActions(actions) menu.exec_(event.globalPos()) From 8eef66c3df8583a8503caefd69e6bd7255fd1498 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 12:29:43 +0100 Subject: [PATCH 214/281] Fix creation of DiscoverResult for pyblish plugins --- openpype/pipeline/create/context.py | 7 ++++--- openpype/pipeline/publish/lib.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 5f1371befa..7672c49eb3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -8,6 +8,9 @@ import inspect from uuid import uuid4 from contextlib import contextmanager +import pyblish.logic +import pyblish.api + from openpype.client import get_assets, get_asset_by_name from openpype.settings import ( get_system_settings, @@ -1619,8 +1622,6 @@ class CreateContext: self._reset_convertor_plugins() def _reset_publish_plugins(self, discover_publish_plugins): - import pyblish.logic - from openpype.pipeline import OpenPypePyblishPluginMixin from openpype.pipeline.publish import ( publish_plugins_discover @@ -1629,7 +1630,7 @@ class CreateContext: # Reset publish plugins self._attr_plugins_by_family = {} - discover_result = DiscoverResult() + discover_result = DiscoverResult(pyblish.api.Plugin) plugins_with_defs = [] plugins_by_targets = [] plugins_mismatch_targets = [] diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 50623e5110..5f95c6695e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -270,7 +270,7 @@ def publish_plugins_discover(paths=None): """ # The only difference with `pyblish.api.discover` - result = DiscoverResult() + result = DiscoverResult(pyblish.api.Plugin) plugins = dict() plugin_names = [] From 70ab3cd2ab75d29e6ceb60e793a9ffc85ecb3f5c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 14:14:45 +0100 Subject: [PATCH 215/281] fix newly added creators on refresh --- openpype/tools/publisher/widgets/create_widget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index dbf075c216..ef9c5b98fe 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -457,13 +457,14 @@ class CreateWidget(QtWidgets.QWidget): # TODO add details about creator new_creators.add(identifier) if identifier in existing_items: + is_new = False item = existing_items[identifier] else: + is_new = True item = QtGui.QStandardItem() item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - self._creators_model.appendRow(item) item.setData(creator_item.label, QtCore.Qt.DisplayRole) item.setData(creator_item.show_order, CREATOR_SORT_ROLE) @@ -473,6 +474,8 @@ class CreateWidget(QtWidgets.QWidget): CREATOR_THUMBNAIL_ENABLED_ROLE ) item.setData(creator_item.family, FAMILY_ROLE) + if is_new: + self._creators_model.appendRow(item) # Remove families that are no more available for identifier in (old_creators - new_creators): From a305de03f0116e06e490266d8f34c967f12d32f6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 16 Feb 2023 15:31:33 +0000 Subject: [PATCH 216/281] Lower tolerance for framerate difference. --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4c8b11ecd3..b920428b20 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3422,7 +3422,7 @@ def convert_to_maya_fps(fps): min_difference = min(differences) min_index = differences.index(min_difference) supported_framerate = float_framerates[min_index] - if round(min_difference) != 0: + if min_difference > 0.1: raise ValueError( "Framerate \"{}\" strays too far from any supported framerate" " in Maya. Closest supported framerate is \"{}\"".format( From 8a5831c6fe6e4bf434d45af457da013df4def88a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 17:11:00 +0100 Subject: [PATCH 217/281] check for 'pyside6' when filling kwargs for file dialog --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 765d32b3d5..18be746d49 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -621,7 +621,7 @@ class FilesWidget(QtWidgets.QWidget): "caption": "Work Files", "filter": ext_filter } - if qtpy.API in ("pyside", "pyside2"): + if qtpy.API in ("pyside", "pyside2", "pyside6"): kwargs["dir"] = self._workfiles_root else: kwargs["directory"] = self._workfiles_root From 7109c6ea1a483ed4b95a77e3dec2e379e569a2c2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Feb 2023 16:20:26 +0800 Subject: [PATCH 218/281] update the naming convention for the render outputs --- openpype/hosts/max/api/lib_renderproducts.py | 8 ++- openpype/hosts/max/api/lib_rendersettings.py | 25 ++++++- .../plugins/publish/submit_3dmax_deadline.py | 66 +++++++++++++++++-- .../plugins/publish/submit_publish_job.py | 5 +- .../defaults/project_settings/deadline.json | 3 +- .../defaults/project_settings/max.json | 2 +- .../schema_project_deadline.json | 5 ++ 7 files changed, 104 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index b54e2513e1..e09934e5de 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -25,11 +25,17 @@ class RenderProducts(object): def render_product(self, container): folder = rt.maxFilePath + file = rt.maxFileName folder = folder.replace("\\", "/") setting = self._project_settings render_folder = get_default_render_folder(setting) + filename, ext = os.path.splitext(file) + + output_file = os.path.join(folder, + render_folder, + filename, + container) - output_file = os.path.join(folder, render_folder, container) context = get_current_project_asset() startFrame = context["data"].get("frameStart") endFrame = context["data"].get("frameEnd") + 1 diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index bc9b02bc77..d4784d9dfb 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -44,11 +44,15 @@ class RenderSettings(object): def set_renderoutput(self, container): folder = rt.maxFilePath # hard-coded, should be customized in the setting + file = rt.maxFileName folder = folder.replace("\\", "/") # hard-coded, set the renderoutput path setting = self._project_settings render_folder = get_default_render_folder(setting) - output_dir = os.path.join(folder, render_folder) + filename, ext = os.path.splitext(file) + output_dir = os.path.join(folder, + render_folder, + filename) if not os.path.exists(output_dir): os.makedirs(output_dir) # hard-coded, should be customized in the setting @@ -139,3 +143,22 @@ class RenderSettings(object): target, renderpass = str(renderlayer_name).split(":") aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) render_elem.SetRenderElementFileName(i, aov_name) + + def get_renderoutput(self, container, output_dir): + output = os.path.join(output_dir, container) + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + outputFilename = "{0}..{1}".format(output, img_fmt) + return outputFilename + + def get_render_element(self): + orig_render_elem = list() + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 0: + return + + for i in range(render_elem_num): + render_element = render_elem.GetRenderElementFilename(i) + orig_render_elem.append(render_element) + + return orig_render_elem diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index dec951da7a..9316e34898 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -5,8 +5,10 @@ import getpass import requests import pyblish.api - +from pymxs import runtme as rt from openpype.pipeline import legacy_io +from openpype.hosts.max.api.lib import get_current_renderer +from openpype.hosts.max.api.lib_rendersettings import RenderSettings class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): @@ -26,6 +28,7 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): group = None deadline_pool = None deadline_pool_secondary = None + framePerTask = 1 def process(self, instance): context = instance.context @@ -55,10 +58,30 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): anatomy_filled = anatomy_data.format(template_data) template_filled = anatomy_filled["publish"]["path"] filepath = os.path.normpath(template_filled) - + filepath = filepath.replace("\\", "/") self.log.info( "Using published scene for render {}".format(filepath) ) + if not os.path.exists(filepath): + self.log.error("published scene does not exist!") + + new_scene = self._clean_name(filepath) + # use the anatomy data for setting up the path of the files + orig_scene = self._clean_name(instance.context.data["currentFile"]) + expected_files = instance.data.get("expectedFiles") + + new_exp = [] + for file in expected_files: + new_file = str(file).replace(orig_scene, new_scene) + new_exp.append(new_file) + + instance.data["expectedFiles"] = new_exp + + metadata_folder = instance.data.get("publishRenderMetadataFolder") + if metadata_folder: + metadata_folder = metadata_folder.replace(orig_scene, + new_scene) + instance.data["publishRenderMetadataFolder"] = metadata_folder payload = { "JobInfo": { @@ -78,7 +101,8 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): "Frames": frames, "ChunkSize": self.chunk_size, "Priority": instance.data.get("priority", self.priority), - "Comment": comment + "Comment": comment, + "FramesPerTask": self.framePerTask }, "PluginInfo": { # Input @@ -131,8 +155,37 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): self.log.info("Ensuring output directory exists: %s" % dirname) os.makedirs(dirname) + plugin_data = {} + if self.use_published: + old_output_dir = os.path.dirname(expected_files[0]) + output_beauty = RenderSettings().get_renderoutput(instance.name, + old_output_dir) + output_beauty = output_beauty.replace(orig_scene, new_scene) + output_beauty = output_beauty.replace("\\", "/") + plugin_data["RenderOutput"] = output_beauty + + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + if ( + renderer == "ART_Renderer" or + renderer == "Redshift_Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer" + ): + render_elem_list = RenderSettings().get_render_element() + for i, render_element in enumerate(render_elem_list): + render_element = render_element.replace(orig_scene, new_scene) + plugin_data["RenderElementOutputFilename%d" % i] = render_element + + self.log.debug("plugin data:{}".format(plugin_data)) + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) payload["JobInfo"].update(output_data) + payload["PluginInfo"].update(plugin_data) self.submit(instance, payload) @@ -158,8 +211,13 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) # Store output dir for unified publisher (expectedFilesequence) expected_files = instance.data["expectedFiles"] - self.log.info("exp:{}".format(expected_files)) output_dir = os.path.dirname(expected_files[0]) instance.data["toBeRenderedOn"] = "deadline" instance.data["outputDir"] = output_dir instance.data["deadlineSubmissionJob"] = response.json() + + def rename_render_element(self): + pass + + def _clean_name(self, path): + return os.path.splitext(os.path.basename(path))[0] \ No newline at end of file diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index b70301ab7e..34fa8a8c03 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -293,8 +293,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "Group": self.deadline_group, "Pool": instance.data.get("primaryPool"), "SecondaryPool": instance.data.get("secondaryPool"), - - "OutputDirectory0": output_dir + # ensure the outputdirectory with correct slashes + "OutputDirectory0": output_dir.replace("\\", "/") }, "PluginInfo": { "Version": self.plugin_pype_version, @@ -1000,6 +1000,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), } + submission_type = instance.data["toBeRenderedOn"] if submission_type == "deadline": # get default deadline webservice url from deadline module self.deadline_url = instance.context.data["defaultDeadline"] diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 0fab284c66..25d2988982 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -44,7 +44,8 @@ "chunk_size": 10, "group": "none", "deadline_pool": "", - "deadline_pool_secondary": "" + "deadline_pool_secondary": "", + "framePerTask": 1 }, "NukeSubmitDeadline": { "enabled": true, diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 651a074a08..617e298310 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,6 +1,6 @@ { "RenderSettings": { - "default_render_image_folder": "renders/max", + "default_render_image_folder": "renders/3dsmax", "aov_separator": "underscore", "image_format": "exr" } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index afefd3266a..f71a253105 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -249,6 +249,11 @@ "type": "text", "key": "deadline_pool_secondary", "label": "Deadline pool (secondary)" + }, + { + "type": "number", + "key": "framePerTask", + "label": "Frame Per Task" } ] }, From f1843b1120575e5a40c87c09ccc222e785c23d91 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Feb 2023 16:24:19 +0800 Subject: [PATCH 219/281] hound fix --- openpype/hosts/max/api/lib_rendersettings.py | 4 ++-- .../deadline/plugins/publish/submit_3dmax_deadline.py | 9 ++++----- .../deadline/plugins/publish/submit_publish_job.py | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index d4784d9dfb..92f716ba54 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -158,7 +158,7 @@ class RenderSettings(object): return for i in range(render_elem_num): - render_element = render_elem.GetRenderElementFilename(i) - orig_render_elem.append(render_element) + render_element = render_elem.GetRenderElementFilename(i) + orig_render_elem.append(render_element) return orig_render_elem diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index 9316e34898..656f550e9e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -5,7 +5,6 @@ import getpass import requests import pyblish.api -from pymxs import runtme as rt from openpype.pipeline import legacy_io from openpype.hosts.max.api.lib import get_current_renderer from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -175,9 +174,9 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): renderer == "Quicksilver_Hardware_Renderer" ): render_elem_list = RenderSettings().get_render_element() - for i, render_element in enumerate(render_elem_list): - render_element = render_element.replace(orig_scene, new_scene) - plugin_data["RenderElementOutputFilename%d" % i] = render_element + for i, element in enumerate(render_elem_list): + element = element.replace(orig_scene, new_scene) + plugin_data["RenderElementOutputFilename%d" % i] = element # noqa self.log.debug("plugin data:{}".format(plugin_data)) self.log.info("Scene name was switched {} -> {}".format( @@ -220,4 +219,4 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): pass def _clean_name(self, path): - return os.path.splitext(os.path.basename(path))[0] \ No newline at end of file + return os.path.splitext(os.path.basename(path))[0] diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 34fa8a8c03..c347f50c59 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -339,7 +339,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + response = requests.post(url, json=payload, timeout=10, verify=False) if not response.ok: raise Exception(response.text) @@ -1000,7 +1000,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), } - submission_type = instance.data["toBeRenderedOn"] if submission_type == "deadline": # get default deadline webservice url from deadline module self.deadline_url = instance.context.data["defaultDeadline"] From bf3bf31d999c179e2a88e850e4f7db8e6c7082d3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Feb 2023 16:40:43 +0800 Subject: [PATCH 220/281] fix the bug of reference before assignment --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index c347f50c59..22a5069ac2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -339,7 +339,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10, verify=False) + response = requests.post(url, json=payload, timeout=10) if not response.ok: raise Exception(response.text) @@ -961,6 +961,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ''' render_job = None + submission_type = "" if instance.data.get("toBeRenderedOn") == "deadline": render_job = data.pop("deadlineSubmissionJob", None) submission_type = "deadline" From e6bf6add2f0b25d93dfd0d822b3d1d5a4393f1a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Feb 2023 10:48:15 +0100 Subject: [PATCH 221/281] modify integrate ftrack instances to be able upload origin filename --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index d6cb3daf0d..75f43cb22f 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -56,6 +56,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "reference": "reference" } keep_first_subset_name_for_review = True + upload_reviewable_with_origin_name = False asset_versions_status_profiles = [] additional_metadata_keys = [] @@ -294,6 +295,13 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) # Add item to component list component_list.append(review_item) + if self.upload_reviewable_with_origin_name: + origin_name_component = copy.deepcopy(review_item) + filename = os.path.basename(repre_path) + origin_name_component["component_data"]["name"] = ( + os.path.splitext(filename)[0] + ) + component_list.append(origin_name_component) # Duplicate thumbnail component for all not first reviews if first_thumbnail_component is not None: From f42831ecb04d163e647f68db8b008530926de81e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Feb 2023 10:48:48 +0100 Subject: [PATCH 222/281] added settings for original basename upload --- .../defaults/project_settings/ftrack.json | 3 ++- .../projects_schema/schema_project_ftrack.json | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index cdf861df4a..f3f2345a0f 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -488,7 +488,8 @@ }, "keep_first_subset_name_for_review": true, "asset_versions_status_profiles": [], - "additional_metadata_keys": [] + "additional_metadata_keys": [], + "upload_reviewable_with_origin_name": false }, "IntegrateFtrackFarmStatus": { "farm_status_profiles": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index da414cc961..7050721742 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -1037,6 +1037,21 @@ {"fps": "FPS"}, {"code": "Codec"} ] + }, + { + "type": "separator" + }, + { + "type": "boolean", + "key": "upload_reviewable_with_origin_name", + "label": "Upload reviewable with origin name" + }, + { + "type": "label", + "label": "Note: Reviewable will be uploaded twice into ftrack when enabled. One with original name and second with required 'ftrackreview-mp4'. That may cause dramatic increase of ftrack storage usage." + }, + { + "type": "separator" } ] }, From 40ce393b808a60d37454712cbaa44609afe379d0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Feb 2023 12:28:32 +0100 Subject: [PATCH 223/281] swap workfile and review settings --- openpype/hosts/tvpaint/plugins/create/create_review.py | 4 ++++ openpype/hosts/tvpaint/plugins/create/create_workfile.py | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 423c3ab30f..0164d58262 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -9,12 +9,16 @@ class TVPaintReviewCreator(TVPaintAutoCreator): label = "Review" icon = "ei.video" + # Settings + active_on_create = True + def apply_settings(self, project_settings, system_settings): plugin_settings = ( project_settings["tvpaint"]["create"]["create_review"] ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] + self.active_on_create = plugin_settings["active_on_create"] def create(self): existing_instance = None diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index cc64936bdd..d56a6c59e7 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -9,16 +9,12 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): label = "Workfile" icon = "fa.file-o" - # Settings - active_on_create = True - def apply_settings(self, project_settings, system_settings): plugin_settings = ( project_settings["tvpaint"]["create"]["create_workfile"] ) self.default_variant = plugin_settings["default_variant"] self.default_variants = plugin_settings["default_variants"] - self.active_on_create = plugin_settings["active_on_create"] def create(self): existing_instance = None From 173b404d0e2af1712f9e7cf4d9f60ac5ea030570 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Feb 2023 13:06:52 +0100 Subject: [PATCH 224/281] swap also usage of settings in review and workfile creators --- openpype/hosts/tvpaint/plugins/create/create_review.py | 2 ++ openpype/hosts/tvpaint/plugins/create/create_workfile.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py index 0164d58262..886dae7c39 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -47,6 +47,8 @@ class TVPaintReviewCreator(TVPaintAutoCreator): "task": task_name, "variant": self.default_variant } + if not self.active_on_create: + data["active"] = False new_instance = CreatedInstance( self.family, subset_name, data, self diff --git a/openpype/hosts/tvpaint/plugins/create/create_workfile.py b/openpype/hosts/tvpaint/plugins/create/create_workfile.py index d56a6c59e7..41347576d5 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_workfile.py +++ b/openpype/hosts/tvpaint/plugins/create/create_workfile.py @@ -43,8 +43,6 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator): "task": task_name, "variant": self.default_variant } - if not self.active_on_create: - data["active"] = False new_instance = CreatedInstance( self.family, subset_name, data, self From 9d600e45880c8e8ab107b87ff14495003d1a76a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Feb 2023 13:20:29 +0100 Subject: [PATCH 225/281] OP-4974 - fix - missing set of frame range when opening scene Added check for not up-to-date loaded containers. --- openpype/hosts/harmony/api/pipeline.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 4b9849c190..686770b64e 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -126,10 +126,6 @@ def check_inventory(): def application_launch(event): """Event that is executed after Harmony is launched.""" - # FIXME: This is breaking server <-> client communication. - # It is now moved so it it manually called. - # ensure_scene_settings() - # check_inventory() # fills OPENPYPE_HARMONY_JS pype_harmony_path = Path(__file__).parent.parent / "js" / "PypeHarmony.js" pype_harmony_js = pype_harmony_path.read_text() @@ -146,6 +142,9 @@ def application_launch(event): harmony.send({"script": script}) inject_avalon_js() + ensure_scene_settings() + check_inventory() + def export_template(backdrops, nodes, filepath): """Export Template to file. From a28cc008302328fe7b615551a567cdd8496baa94 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Feb 2023 21:13:14 +0800 Subject: [PATCH 226/281] add multipass as setting and regex filter in publish job setting --- openpype/hosts/max/api/lib.py | 5 +++++ .../plugins/publish/submit_3dmax_deadline.py | 17 ++++++++++++++++- .../plugins/publish/submit_publish_job.py | 3 ++- .../defaults/project_settings/deadline.json | 3 +++ .../settings/defaults/project_settings/max.json | 3 ++- .../projects_schema/schema_project_max.json | 5 +++++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 0477b43182..a28ec4b6af 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -151,3 +151,8 @@ def set_framerange(startFrame, endFrame): if startFrame is not None and endFrame is not None: frameRange = "{0}-{1}".format(startFrame, endFrame) rt.rendPickupFrames = frameRange + +def get_multipass_setting(project_setting=None): + return (project_setting["max"] + ["RenderSettings"] + ["multipass"]) diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index 656f550e9e..d7716527b1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -6,7 +6,11 @@ import requests import pyblish.api from openpype.pipeline import legacy_io -from openpype.hosts.max.api.lib import get_current_renderer +from openpype.settings import get_project_settings +from openpype.hosts.max.api.lib import ( + get_current_renderer, + get_multipass_setting +) from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -154,7 +158,18 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): self.log.info("Ensuring output directory exists: %s" % dirname) os.makedirs(dirname) + plugin_data = {} + project_setting = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + + multipass = get_multipass_setting(project_setting) + if multipass: + plugin_data["DisableMultipass"] = 0 + else: + plugin_data["DisableMultipass"] = 1 + if self.use_published: old_output_dir = os.path.dirname(expected_files[0]) output_beauty = RenderSettings().get_renderoutput(instance.name, diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 22a5069ac2..505b940356 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -126,7 +126,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE "harmony": [r".*"], # for everything from AE - "celaction": [r".*"]} + "celaction": [r".*"], + "max": [r".*"]} environ_job_filter = [ "OPENPYPE_METADATA_FILE" diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 24d1f7405b..7a5903d8e0 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -115,6 +115,9 @@ ], "harmony": [ ".*" + ], + "max": [ + ".*" ] } } diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 617e298310..84e0c7dba7 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -2,6 +2,7 @@ "RenderSettings": { "default_render_image_folder": "renders/3dsmax", "aov_separator": "underscore", - "image_format": "exr" + "image_format": "exr", + "multipass": true } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index fbd9358c74..8a283c1acc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -44,6 +44,11 @@ {"tga": "tga"}, {"dds": "dds"} ] + }, + { + "type": "boolean", + "key": "multipass", + "label": "multipass" } ] } From 281d53bb01d1da9504be0a19ae0a004804ac8cc9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Feb 2023 21:14:43 +0800 Subject: [PATCH 227/281] hound fix --- openpype/hosts/max/api/lib.py | 1 + .../modules/deadline/plugins/publish/submit_3dmax_deadline.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index a28ec4b6af..ecea8b5541 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -152,6 +152,7 @@ def set_framerange(startFrame, endFrame): frameRange = "{0}-{1}".format(startFrame, endFrame) rt.rendPickupFrames = frameRange + def get_multipass_setting(project_setting=None): return (project_setting["max"] ["RenderSettings"] diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py index d7716527b1..ed448abe1f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py @@ -161,8 +161,8 @@ class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): plugin_data = {} project_setting = get_project_settings( - legacy_io.Session["AVALON_PROJECT"] - ) + legacy_io.Session["AVALON_PROJECT"] + ) multipass = get_multipass_setting(project_setting) if multipass: From e246afe55b2ecee3f726b54c27b85a5a45f3dcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:07:24 +0100 Subject: [PATCH 228/281] removing typo --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 485ae7f4ee..514ffb62c0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ OpenPype [![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2022-lightgrey?labelColor=303846) -this Introduction ------------ From 8afd618a0d2b2abbe6441d6d9c3d5c57170424ac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Feb 2023 15:38:43 +0100 Subject: [PATCH 229/281] updating workflows --- .../workflows/miletone_release_trigger.yml | 47 ++++++++++++ .github/workflows/nightly_merge.yml | 29 ------- .github/workflows/prerelease.yml | 67 ---------------- .github/workflows/release.yml | 76 ------------------- 4 files changed, 47 insertions(+), 172 deletions(-) create mode 100644 .github/workflows/miletone_release_trigger.yml delete mode 100644 .github/workflows/nightly_merge.yml delete mode 100644 .github/workflows/prerelease.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/miletone_release_trigger.yml b/.github/workflows/miletone_release_trigger.yml new file mode 100644 index 0000000000..b5b8aab1dc --- /dev/null +++ b/.github/workflows/miletone_release_trigger.yml @@ -0,0 +1,47 @@ +name: Milestone Release [trigger] + +on: + workflow_dispatch: + inputs: + milestone: + required: true + release-type: + type: choice + description: What release should be created + options: + - release + - pre-release + milestone: + types: closed + + +jobs: + milestone-title: + runs-on: ubuntu-latest + outputs: + milestone: ${{ steps.milestoneTitle.outputs.value }} + steps: + - name: Switch input milestone + uses: haya14busa/action-cond@v1 + id: milestoneTitle + with: + cond: ${{ inputs.milestone == '' }} + if_true: ${{ github.event.milestone.title }} + if_false: ${{ inputs.milestone }} + - name: Print resulted milestone + run: | + echo "${{ steps.milestoneTitle.outputs.value }}" + + call-ci-tools-milestone-release: + needs: milestone-title + uses: ynput/ci-tools/.github/workflows/milestone_release_ref.yml@main + with: + milestone: ${{ needs.milestone-title.outputs.milestone }} + repo-owner: ${{ github.event.repository.owner.login }} + repo-name: ${{ github.event.repository.name }} + version-py-path: "./openpype/version.py" + pyproject-path: "./pyproject.toml" + secrets: + token: ${{ secrets.YNPUT_BOT_TOKEN }} + user_email: ${{ secrets.CI_EMAIL }} + user_name: ${{ secrets.CI_USER }} diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml deleted file mode 100644 index 1776d7a464..0000000000 --- a/.github/workflows/nightly_merge.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Dev -> Main - -on: - schedule: - - cron: '21 3 * * 3,6' - workflow_dispatch: - -jobs: - develop-to-main: - - runs-on: ubuntu-latest - - steps: - - name: 🚛 Checkout Code - uses: actions/checkout@v2 - - - name: 🔨 Merge develop to main - uses: everlytic/branch-merge@1.1.0 - with: - github_token: ${{ secrets.YNPUT_BOT_TOKEN }} - source_ref: 'develop' - target_branch: 'main' - commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' - - - name: Invoke pre-release workflow - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: Nightly Prerelease - token: ${{ secrets.YNPUT_BOT_TOKEN }} diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml deleted file mode 100644 index 571b0339e1..0000000000 --- a/.github/workflows/prerelease.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Nightly Prerelease - -on: - workflow_dispatch: - - -jobs: - create_nightly: - runs-on: ubuntu-latest - - steps: - - name: 🚛 Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Install Python requirements - run: pip install gitpython semver PyGithub - - - name: 🔎 Determine next version type - id: version_type - run: | - TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) - echo "type=${TYPE}" >> $GITHUB_OUTPUT - - - name: 💉 Inject new version into files - id: version - if: steps.version_type.outputs.type != 'skip' - run: | - NEW_VERSION_TAG=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) - echo "next_tag=${NEW_VERSION_TAG}" >> $GITHUB_OUTPUT - - - name: 💾 Commit and Tag - id: git_commit - if: steps.version_type.outputs.type != 'skip' - run: | - git config user.email ${{ secrets.CI_EMAIL }} - git config user.name ${{ secrets.CI_USER }} - git checkout main - git pull - git add . - git commit -m "[Automated] Bump version" - tag_name="CI/${{ steps.version.outputs.next_tag }}" - echo $tag_name - git tag -a $tag_name -m "nightly build" - - - name: Push to protected main branch - uses: CasperWA/push-protected@v2.10.0 - with: - token: ${{ secrets.YNPUT_BOT_TOKEN }} - branch: main - tags: true - unprotect_reviews: true - - - name: 🔨 Merge main back to develop - uses: everlytic/branch-merge@1.1.0 - if: steps.version_type.outputs.type != 'skip' - with: - github_token: ${{ secrets.YNPUT_BOT_TOKEN }} - source_ref: 'main' - target_branch: 'develop' - commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 0b4c8af2c7..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Stable Release - -on: - release: - types: - - prereleased - -jobs: - create_release: - runs-on: ubuntu-latest - if: github.actor != 'pypebot' - - steps: - - name: 🚛 Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install Python requirements - run: pip install gitpython semver PyGithub - - - name: 💉 Inject new version into files - id: version - run: | - NEW_VERSION=$(python ./tools/ci_tools.py --finalize ${GITHUB_REF#refs/*/}) - LAST_VERSION=$(python ./tools/ci_tools.py --lastversion release) - - echo "current_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - echo "last_release=${LAST_VERSION}" >> $GITHUB_OUTPUT - echo "release_tag=${NEW_VERSION}" >> $GITHUB_OUTPUT - - - name: 💾 Commit and Tag - id: git_commit - if: steps.version.outputs.release_tag != 'skip' - run: | - git config user.email ${{ secrets.CI_EMAIL }} - git config user.name ${{ secrets.CI_USER }} - git add . - git commit -m "[Automated] Release" - tag_name="${{ steps.version.outputs.release_tag }}" - git tag -a $tag_name -m "stable release" - - - name: 🔏 Push to protected main branch - if: steps.version.outputs.release_tag != 'skip' - uses: CasperWA/push-protected@v2.10.0 - with: - token: ${{ secrets.YNPUT_BOT_TOKEN }} - branch: main - tags: true - unprotect_reviews: true - - - name: 🚀 Github Release - if: steps.version.outputs.release_tag != 'skip' - uses: ncipollo/release-action@v1 - with: - tag: ${{ steps.version.outputs.release_tag }} - token: ${{ secrets.YNPUT_BOT_TOKEN }} - - - name: ☠ Delete Pre-release - if: steps.version.outputs.release_tag != 'skip' - uses: cb80/delrel@latest - with: - tag: "${{ steps.version.outputs.current_version }}" - - - name: 🔁 Merge main back to develop - if: steps.version.outputs.release_tag != 'skip' - uses: everlytic/branch-merge@1.1.0 - with: - github_token: ${{ secrets.YNPUT_BOT_TOKEN }} - source_ref: 'main' - target_branch: 'develop' - commit_message_template: '[Automated] Merged release {source_ref} into {target_branch}' From 3af80e97e81204ee461acc58ac73cb402f505a6f Mon Sep 17 00:00:00 2001 From: Ynbot Date: Fri, 17 Feb 2023 14:54:45 +0000 Subject: [PATCH 230/281] [Automated] Release --- CHANGELOG.md | 76 +++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 8 ++--- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da167763b..8a37886deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,81 @@ # Changelog + +## [3.15.1](https://github.com/ynput/OpenPype/tree/3.15.1) + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.1...3.15.0) + +### **🆕 New features** + + +

+Maya: Xgen (3d / maya ) - #4256 + + +___ + + +## Brief description +Initial Xgen implementation. + +## Description +Client request of Xgen pipeline. + + + + +___ + + +
+ +### **🚀 Enhancements** + + +
+Adding path validator for non-maya nodes (3d / maya ) - #4271 + + +___ + + +## Brief description +Adding a path validator for filepaths from non-maya nodes, which are created by plugins such as Renderman, Yeti and abcImport. + +## Description +As File Path Editor cannot catch the wrong filenpaths from non-maya nodes such as AlembicNodes, It is neccessary to have a new validator to ensure the existence of the filepaths from the nodes. + + + + +___ + + +
+ +### **🐛 Bug fixes** + + +
+Fix features for gizmo menu (2d / nuke ) - #4280 + + +___ + + +## Brief description +Fix features for the Gizmo Menu project settings (shortcut for python type of usage and file type of usage functionality) + + + + +___ + + +
+ + + ## [3.15.0](https://github.com/ynput/OpenPype/tree/HEAD) [Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.10...HEAD) diff --git a/openpype/version.py b/openpype/version.py index 6d060656cb..72d6b64c60 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.6" +__version__ = "3.15.1" diff --git a/pyproject.toml b/pyproject.toml index a872ed3609..d1d5c8e2d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.0" # OpenPype +version = "3.15.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" @@ -114,15 +114,15 @@ build-backend = "poetry.core.masonry.api" # https://pip.pypa.io/en/stable/cli/pip_install/#requirement-specifiers [openpype.qtbinding.windows] package = "PySide2" -version = "5.15.2" +version = "3.15.1" [openpype.qtbinding.darwin] package = "PySide6" -version = "6.4.1" +version = "3.15.1" [openpype.qtbinding.linux] package = "PySide2" -version = "5.15.2" +version = "3.15.1" # TODO: we will need to handle different linux flavours here and # also different macos versions too. From 15c9f71633c1cb598618e7629ef496b6d1abeec7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Feb 2023 16:12:04 +0100 Subject: [PATCH 231/281] updating changelog --- CHANGELOG.md | 383 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 378 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a37886deb..752a7dc1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,82 +3,455 @@ ## [3.15.1](https://github.com/ynput/OpenPype/tree/3.15.1) -[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.1...3.15.0) +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.0...3.15.1) ### **🆕 New features** + +
Maya: Xgen (3d / maya ) - #4256 + ___ + ## Brief description + Initial Xgen implementation. + + ## Description + Client request of Xgen pipeline. + + + ___ +
+ ### **🚀 Enhancements** + +
Adding path validator for non-maya nodes (3d / maya ) - #4271 + ___ + ## Brief description + Adding a path validator for filepaths from non-maya nodes, which are created by plugins such as Renderman, Yeti and abcImport. + + ## Description + As File Path Editor cannot catch the wrong filenpaths from non-maya nodes such as AlembicNodes, It is neccessary to have a new validator to ensure the existence of the filepaths from the nodes. + + + ___ +
+ + +
+Deadline: Allow disabling strict error check in Maya submissions (3d / maya / deadline ) - #4420 + + + +___ + + + +## Brief description + +DL by default has Strict error checking, but some errors are not fatal. + + + +## Description + +This allows to set profile based on Task and Subset values to temporarily disable Strict Error Checks.Subset and task names should support regular expressions. (not wildcard notation though). + + + + + + + +___ + + + +
+ + + +
+Houdini: New publisher code tweak (3d / houdini ) - #4374 + + + +___ + + + +## Brief description + +This is cosmetics only - the previous code to me felt quite unreadable due to the lengthy strings being used. + + + +## Description + +Code should do roughly the same, but just be reformatted. + + + + + + + +___ + + + +
+ + + +
+Houdini: Do not visualize the hidden OpenPypeContext node (3d / houdini ) - #4382 + + + +___ + + + +## Brief description + +Using the new publisher UI would generate a visible 'null' locator at the origin. It's confusing to the user since it's supposed to be 'hidden'. + + + +## Description + +Before this PR the user would see a locator/null at the origin which was the 'hidden' `/obj/OpenPypeContext` node. This null would suddenly appear if the user would've ever opened the Publisher UI once.After this PR it will not show:Nice and tidy. + + + + + + + +___ + + + +
+ + + +
+Global: supporting `OPENPYPE_TMPDIR` in staging dir maker (editorial / hiero ) - #4398 + + + +___ + + + +## Brief description + +Productions can use OPENPYPE_TMPDIR for staging temp publishing directory + + + +## Description + +Studios were demanding to be able to configure their own shared storages as temporary staging directories. Template formatting is also supported with optional keys formatting and following anatomy keys: - root[work | ] - project[name | code] + + + + + + + +___ + + + +
+ + ### **🐛 Bug fixes** + + +
+Maya: Fix Validate Attributes plugin (3d / maya ) - #4401 + + + +___ + + + +## Brief description + +Code was broken. So either plug-in was unused or it had gone unnoticed. + + + +## Description + +Looking at the commit history of the plug-in itself it seems this might have been broken somewhere between two to three years. I think it's broken since two years since this commit.Should this plug-in be removed completely?@tokejepsen Is there still a use case where we should have this plug-in? (You created the original one) + + + + + + + +___ + + + +
+ + + +
+Maya: Ignore workfile lock in Untitled scene (3d / maya ) - #4414 + + + +___ + + + +## Brief description + +Skip workfile lock check if current scene is 'Untitled'. + + + + + + + +___ + + + +
+ + + +
+Maya: fps rounding - OP-2549 (3d / maya ) - #4424 + + + +___ + + + +## Brief description + +When FPS is registered in for example Ftrack and round either down or up (floor/ceil), comparing to Maya FPS can fail. Example:23.97 (Ftrack/Mongo) != 23.976023976023978 (Maya) + + + +## Description + +Since Maya only has a select number of supported framerates, I've taken the approach of converting any fps to supported framerates in Maya. We validate the input fps to make sure they are supported in Maya in two ways:Whole Numbers - are validated straight against the supported framerates in Maya.Demical Numbers - we find the closest supported framerate in Maya. If the difference to the closest supported framerate, is more than 0.5 we'll throw an error.If Maya ever supports arbitrary framerates, then we might have a problem but I'm not holding my breath... + + + + + + + +___ + + + +
+ + + +
+Strict Error Checking Default (3d / maya ) - #4457 + + + +___ + + + +## Brief description + +Provide default of strict error checking for instances created prior to PR. + + + + + + + +___ + + + +
+ + + +
+Create: Enhance instance & context changes (3d / houdini,after effects,3dsmax ) - #4375 + + + +___ + + + +## Brief description + +Changes of instances and context have complex, hard to get structure. The structure did not change but instead of complex dictionaries are used objected data. + + + +## Description + +This is poposal of changes data improvement for creators. Implemented `TrackChangesItem` which handles the changes for us. The item is creating changes based on old and new value and can provide information about changed keys or access to full old or new value. Can give the values on any "sub-dictionary".Used this new approach to fix change in houdini and 3ds max and also modified one aftereffects plugin using changes. + + + + + + + +___ + + + +
+ + + +
+Houdini: hotfix condition (3d / houdini ) - #4391 + + + +___ + + + +## Hotfix + + + +This is fixing bug introduced int #4374 + + + +___ + + + +
+ + + +
+Houdini: Houdini shelf tools fixes (3d / houdini ) - #4428 + + + +___ + + + +## Brief description + +Fix Houdini shelf tools. + + + +## Description + +Use `label` as mandatory key instead of `name`. Changed how shelves are created. If the script is empty it is gracefully skipping it instead of crashing. + + + + + + + +___ + + + +
+ + +
Fix features for gizmo menu (2d / nuke ) - #4280 + ___ + ## Brief description + Fix features for the Gizmo Menu project settings (shortcut for python type of usage and file type of usage functionality) - - ___ +
-## [3.15.0](https://github.com/ynput/OpenPype/tree/HEAD) +## [3.15.0](https://github.com/ynput/OpenPype/tree/3.15.0) -[Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.10...HEAD) +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.10...3.15.0) **Deprecated:** From aaba8c1f7be4dca894ab51c6a84092b9759e88e8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 07:37:50 +0000 Subject: [PATCH 232/281] Fix attributes --- openpype/settings/defaults/project_anatomy/attributes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index 0cc414fb69..bf8bbef8de 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -23,4 +23,4 @@ ], "tools_env": [], "active": true -} +} \ No newline at end of file From 9875185d7813e6cdf0212537b6a9eef0bb328ab8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Feb 2023 10:39:12 +0100 Subject: [PATCH 233/281] fix CI release [automation bugs] --- CHANGELOG.md | 1008 +++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 6 +- 2 files changed, 872 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 752a7dc1d6..c7ecbc83bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,32 +13,47 @@
Maya: Xgen (3d / maya ) - #4256 - - ___ - - -## Brief description +#### Brief description Initial Xgen implementation. -## Description +#### Description Client request of Xgen pipeline. +___ + +
+ + + +
+Data exchange cameras for 3d Studio Max (3d / 3dsmax ) - #4376 + +___ + +#### Brief description + +Add Camera Family into the 3d Studio Max + + + +#### Description + +Adding Camera Extractors(extract abc camera and extract fbx camera) and validators(for camera contents) into 3dMaxAlso add the extractor for exporting 3d max raw scene (which is also related to 3dMax Scene Family) for camera family + ___ - -
@@ -50,32 +65,23 @@ ___
Adding path validator for non-maya nodes (3d / maya ) - #4271 - - ___ - - -## Brief description +#### Brief description Adding a path validator for filepaths from non-maya nodes, which are created by plugins such as Renderman, Yeti and abcImport. -## Description +#### Description As File Path Editor cannot catch the wrong filenpaths from non-maya nodes such as AlembicNodes, It is neccessary to have a new validator to ensure the existence of the filepaths from the nodes. - - - ___ - -
@@ -83,32 +89,23 @@ ___
Deadline: Allow disabling strict error check in Maya submissions (3d / maya / deadline ) - #4420 - - ___ - - -## Brief description +#### Brief description DL by default has Strict error checking, but some errors are not fatal. -## Description +#### Description This allows to set profile based on Task and Subset values to temporarily disable Strict Error Checks.Subset and task names should support regular expressions. (not wildcard notation though). - - - ___ - -
@@ -116,65 +113,43 @@ ___
Houdini: New publisher code tweak (3d / houdini ) - #4374 - - ___ - - -## Brief description +#### Brief description This is cosmetics only - the previous code to me felt quite unreadable due to the lengthy strings being used. -## Description +#### Description Code should do roughly the same, but just be reformatted. - - - ___ - -
-Houdini: Do not visualize the hidden OpenPypeContext node (3d / houdini ) - #4382 - - +3dsmax: enhance alembic loader update function (3d / 3dsmax ) - #4387 ___ - - -## Brief description - -Using the new publisher UI would generate a visible 'null' locator at the origin. It's confusing to the user since it's supposed to be 'hidden'. - - - -## Description - -Before this PR the user would see a locator/null at the origin which was the 'hidden' `/obj/OpenPypeContext` node. This null would suddenly appear if the user would've ever opened the Publisher UI once.After this PR it will not show:Nice and tidy. - +## Enhancement +This PR is adding update/switch ability to pointcache/alembic loader in 3dsmax and fixing wrong tool shown when clicking on "Manage" item on OpenPype menu, that is now correctly Scene Inventory (but was Subset Manager). +Alembic update has still one caveat - it doesn't cope with changed number of object inside alembic, since loading alembic in max involves creating all those objects as first class nodes. So it will keep the objects in scene, just update path to alembic file on them. ___ - -
@@ -182,32 +157,143 @@ ___
Global: supporting `OPENPYPE_TMPDIR` in staging dir maker (editorial / hiero ) - #4398 - - ___ - - -## Brief description +#### Brief description Productions can use OPENPYPE_TMPDIR for staging temp publishing directory -## Description +#### Description Studios were demanding to be able to configure their own shared storages as temporary staging directories. Template formatting is also supported with optional keys formatting and following anatomy keys: - root[work | ] - project[name | code] +___ + +
+ + + +
+General: Functions for current context (other ) - #4324 + +___ + +#### Brief description + +Defined more functions to receive current context information and added the methods to host integration so host can affect the result. + + + +#### Description + +This is one of steps to reduce usage of `legacy_io.Session`. This change define how to receive current context information -> call functions instead of accessing `legacy_io.Session` or `os.environ` directly. Plus, direct access on session or environments is unfortunatelly not enough for some DCCs where multiple workfiles can be opened at one time which can heavily affect the context but host integration sometimes can't affect that at all.`HostBase` already had implemented `get_current_context`, that was enhanced by adding more specific methods `get_current_project_name`, `get_current_asset_name` and `get_current_task_name`. The same functions were added to `~/openpype/pipeline/cotext_tools.py`. The functions in context tools are calling host integration methods (if are available) otherwise are using environent variables as default implementation does. Also was added `get_current_host_name` to receive host name from registered host if is available or from environment variable. + ___ +
+ +
+Houdini: Do not visualize the hidden OpenPypeContext node (other / houdini ) - #4382 + +___ + +#### Brief description + +Using the new publisher UI would generate a visible 'null' locator at the origin. It's confusing to the user since it's supposed to be 'hidden'. + + + +#### Description + +Before this PR the user would see a locator/null at the origin which was the 'hidden' `/obj/OpenPypeContext` node. This null would suddenly appear if the user would've ever opened the Publisher UI once.After this PR it will not show:Nice and tidy. + + + + +___ + +
+ + + +
+Maya + Blender: Pyblish plugins removed unused `version` and `category` attributes (other ) - #4402 + +___ + +#### Brief description + +Once upon a time in a land far far away there lived a few plug-ins who felt like they didn't belong in generic boxes and felt they needed to be versioned well above others. They tried, but with no success. + + + +#### Description + +Even though they now lived in a universe with elaborate `version` and `category` attributes embedded into their tiny little plug-in DNA this particular deviation has been greatly unused. There is nothing special about the version, nothing special about the category.It does nothing. + + + + +___ + +
+ + + +
+General: Fix original basename frame issues (other ) - #4452 + +___ + +#### Brief description + +Treat `{originalBasename}` in different way then standard files processing. In case template should use `{originalBasename}` the transfers will use them as they are without any changes or handling of frames. + + + +#### Description + +Frames handling is problematic with original basename because their padding can't be defined to match padding in source filenames. Also it limits the usage of functionality to "must have frame at end of fiename". This is proposal how that could be solved by simply ignoring frame handling and using filenames as are on representation. First frame is still stored to representation context but is not used in formatting part. This way we don't have to care about padding of frames at all. + + + + +___ + +
+ + + +
+Publisher: Report also crashed creators and convertors (other ) - #4473 + +___ + +#### Brief description + +Added crashes of creators and convertos discovery (lazy solution). + + + +#### Description + +Report in Publisher also contains information about crashed files caused during creator plugin discovery and convertor plugin discovery. They're not separated into categroies and there is no other information in the report about them, but this helps a lot during development. This change does not need to change format/schema of the report nor UI logic. + + + + +___ +
@@ -219,32 +305,23 @@ ___
Maya: Fix Validate Attributes plugin (3d / maya ) - #4401 - - ___ - - -## Brief description +#### Brief description Code was broken. So either plug-in was unused or it had gone unnoticed. -## Description +#### Description Looking at the commit history of the plug-in itself it seems this might have been broken somewhere between two to three years. I think it's broken since two years since this commit.Should this plug-in be removed completely?@tokejepsen Is there still a use case where we should have this plug-in? (You created the original one) - - - ___ - -
@@ -252,26 +329,17 @@ ___
Maya: Ignore workfile lock in Untitled scene (3d / maya ) - #4414 - - ___ - - -## Brief description +#### Brief description Skip workfile lock check if current scene is 'Untitled'. - - - ___ - -
@@ -279,32 +347,23 @@ ___
Maya: fps rounding - OP-2549 (3d / maya ) - #4424 - - ___ - - -## Brief description +#### Brief description When FPS is registered in for example Ftrack and round either down or up (floor/ceil), comparing to Maya FPS can fail. Example:23.97 (Ftrack/Mongo) != 23.976023976023978 (Maya) -## Description +#### Description Since Maya only has a select number of supported framerates, I've taken the approach of converting any fps to supported framerates in Maya. We validate the input fps to make sure they are supported in Maya in two ways:Whole Numbers - are validated straight against the supported framerates in Maya.Demical Numbers - we find the closest supported framerate in Maya. If the difference to the closest supported framerate, is more than 0.5 we'll throw an error.If Maya ever supports arbitrary framerates, then we might have a problem but I'm not holding my breath... - - - ___ - -
@@ -312,26 +371,17 @@ ___
Strict Error Checking Default (3d / maya ) - #4457 - - ___ - - -## Brief description +#### Brief description Provide default of strict error checking for instances created prior to PR. - - - ___ - -
@@ -339,32 +389,23 @@ ___
Create: Enhance instance & context changes (3d / houdini,after effects,3dsmax ) - #4375 - - ___ - - -## Brief description +#### Brief description Changes of instances and context have complex, hard to get structure. The structure did not change but instead of complex dictionaries are used objected data. -## Description +#### Description This is poposal of changes data improvement for creators. Implemented `TrackChangesItem` which handles the changes for us. The item is creating changes based on old and new value and can provide information about changed keys or access to full old or new value. Can give the values on any "sub-dictionary".Used this new approach to fix change in houdini and 3ds max and also modified one aftereffects plugin using changes. - - - ___ - -
@@ -372,24 +413,15 @@ ___
Houdini: hotfix condition (3d / houdini ) - #4391 - - ___ - - ## Hotfix This is fixing bug introduced int #4374 - - - ___ - -
@@ -397,32 +429,47 @@ ___
Houdini: Houdini shelf tools fixes (3d / houdini ) - #4428 - - ___ - - -## Brief description +#### Brief description Fix Houdini shelf tools. -## Description +#### Description Use `label` as mandatory key instead of `name`. Changed how shelves are created. If the script is empty it is gracefully skipping it instead of crashing. +___ + +
+ + + +
+3dsmax: startup fixes (3d / 3dsmax ) - #4412 + +___ + +#### Brief description + +This is fixing various issues that can occur on some of the 3dsmax versions. + + + +#### Description + +On displays with +4K resolution UI was broken, some 3dsmax versions couldn't process `PYTHONPATH` correctly. This PR is forcing `sys.path` and disabling `QT_AUTO_SCREEN_SCALE_FACTOR` + ___ - -
@@ -430,25 +477,708 @@ ___
Fix features for gizmo menu (2d / nuke ) - #4280 - - ___ - - -## Brief description +#### Brief description Fix features for the Gizmo Menu project settings (shortcut for python type of usage and file type of usage functionality) + + ___ +
+ +
+Photoshop: fix missing legacy io for legacy instances (2d / photoshop,after effects ) - #4467 + +___ + +#### Brief description + +`legacy_io` import was removed, but usage stayed. + + + +#### Description + +Usage of `legacy_io` should be eradicated, in creators it should be replaced by `self.create_context.get_current_project_name/asset_name/task_name`. + + + + +___ +
+
+Fix - addSite loader handles hero version (other / sitesync ) - #4359 + +___ + +#### Brief description + +If adding site to representation presence of hero version is checked, if found hero version is marked to be donwloaded too.Replacing https://github.com/ynput/OpenPype/pull/4191 + + + + +___ + +
+ + + +
+Remove OIIO build for macos (other ) - #4381 + +___ + +## Fix + + + +Since we are not able to provide OpenImageIO tools binaries for macos, we should remove the item from th `pyproject.toml`. This PR is taking care of it. + + + +It is also changing the way `fetch_thirdparty_libs` script works in that it doesn't crash when lib cannot be processed, it only issue warning. + + + + + +Resolves #3858 +___ + +
+ + + +
+General: Attribute definitions fixes (other ) - #4392 + +___ + +#### Brief description + +Fix possible issues with attribute definitions in publisher if there is unknown attribute on an instance. + + + +#### Description + +Source of the issue is that attribute definitions from creator plugin could be "expanded" during `CreatedInstance` initialization. Which would affect all other instances using the same list of attributes -> literally object of list. If the same list object is used in "BaseClass" for other creators it would affect all instances (because of 1 instance). There had to be implemented other changes to fix the issue and keep behavior the same.Object of `CreatedInstance` can be created without reference to creator object. `CreatedInstance` is responsible to give UI attribute definitions (technically is prepared for cases when each instance may have different attribute definitions -> not yet).Attribute definition has added more conditions for `__eq__` method and have implemented `__ne__` method (which is required for Py 2 compatibility). Renamed `AbtractAttrDef` to `AbstractAttrDef` (fix typo). + + + + +___ + +
+ + + +
+Ftrack: Don't force ftrackapp endpoint (other / ftrack ) - #4411 + +___ + +#### Brief description + +Auto-fill of ftrack url don't break custom urls. Custom urls couldn't be used as `ftrackapp.com` is added if is not in the url. + + + +#### Description + +The code was changed in a way that auto-fill is still supported but before `ftrackapp` is added it will try to use url as is. If the connection works as is it is used. + + + + +___ + +
+ + + +
+Fix: DL on MacOS (other ) - #4418 + +___ + +#### Brief description + +This works if DL Openpype plugin Installation Directories is set to level of app bundle (eg. '/Applications/OpenPype 3.15.0.app') + + + + +___ + +
+ + + +
+Photoshop: make usage of layer name in subset name more controllable (other ) - #4432 + +___ + +#### Brief description + +Layer name was previously used in subset name only if multiple instances were being created in single step. This adds explicit toggle. + + + +#### Description + +Toggling this button allows to use layer name in created subset name even if single instance is being created.This follows more closely implementation if AE. + + + + +___ + +
+ + + +
+SiteSync: fix dirmap (other ) - #4436 + +___ + +#### Brief description + +Fixed issue in dirmap in Maya and Nuke + + + +#### Description + +Loads of error were thrown in Nuke console about dictionary value.`AttributeError: 'dict' object has no attribute 'lower'` + + + + +___ + +
+ + + +
+General: Ignore decode error of stdout/stderr in run_subprocess (other ) - #4446 + +___ + +#### Brief description + +Ignore decode errors and replace invalid character (byte) with escaped byte character. + + + +#### Description + +Calling of `run_subprocess` may cause crashes if output contains some unicode character which (for example Polish name of encoder handler). + + + + +___ + +
+ + + +
+Publisher: Fix reopen bug (other ) - #4463 + +___ + +#### Brief description + +Use right name of constant 'ActiveWindow' -> 'WindowActive'. + + + + +___ + +
+ + + +
+Publisher: Fix compatibility of QAction in Publisher (other ) - #4474 + +___ + +#### Brief description + +Fix `QAction` for older version of Qt bindings where QAction requires a parent on initialization. + + + +#### Description + +This bug was discovered in Nuke 11. Fixed by creating QAction when QMenu is already available and can be used as parent. + + + + +___ + +
+ + +### **🔀 Refactored code** + + + + +
+General: Remove 'openpype.api' (other ) - #4413 + +___ + +#### Brief description + +PR is removing `openpype/api.py` file which is causing a lot of troubles and cross-imports. + + + +#### Description + +I wanted to remove the file slowly function by function but it always reappear somewhere in codebase even if most of the functionality imported from there is triggering deprecation warnings. This is small change which may have huge impact.There shouldn't be anything in openpype codebase which is using `openpype.api` anymore so only possible issues are in customized repositories or custom addons. + + + + +___ + +
+ + +### **📃 Documentation** + + + + +
+docs-user-Getting Started adjustments (other ) - #4365 + +___ + +#### Brief description + +Small typo fixes here and there, additional info on install/ running OP. + + + + +___ + +
+ + +### **Merged pull requests** + + + + +
+Renderman support for sample and display filters (3d / maya ) - #4003 + +___ + +#### Brief description + +User can set up both sample and display filters in Openpype settings if they are using Renderman as renderer. + + + +#### Description + +You can preset which sample and display filters for renderman , including the cryptomatte renderpass, in Openpype settings. Once you select which filters to be included in openpype settings and then create render instance for your camera in maya, it would automatically tell the system to generate your selected filters in render settings.The place you can find for setting up the filters: _Maya > Render Settings > Renderman Renderer > Display Filters/ Sample Filters_ + + + + +___ + +
+ + + +
+Maya: Create Arnold options on repair. (3d / maya ) - #4448 + +___ + +#### Brief description + +When validating/repairing we previously required users to open render settings to create the Arnold options. This is done through code now. + + + + +___ + +
+ + + +
+Update Asset field of creator Instances in Maya Template Builder (3d / maya ) - #4470 + +___ + +#### Brief description + +When we build a template with Maya Template Builder, it will update the asset field of the sets (creator instances) that are imported from the template. + + + +#### Description + +When building a template, we also want to define the publishable content in advance: create an instance of a model, or look, etc., to speed up the workflow and reduce the number of questions we are asked. After building a work file from a saved template that contains pre-created instances, the template builder should update the asset field to the current asset. + + + + +___ + +
+ + + +
+Blender: fix import workfile all families (3d / blender ) - #4405 + +___ + +#### Brief description + +Having this feature related to workfile available for any family is absurd. + + + + +___ + +
+ + + +
+Nuke: update rendered frames in latest version (2d / nuke ) - #4362 + +___ + +#### Brief description + +Introduced new field to insert frame(s) to rerender only. + + + +#### Description + +Rendering is expensive, sometimes it is helpful only to re-render changed frames and reuse existing.Artists can in Publisher fill which frame(s) should be re-rendered.If there is already published version of currently publishing subset, all representation files are collected (currently for `render` family only) and then when Nuke is rendering (locally only for now), old published files are copied into into temporary render folder where will be rewritten only by frames explicitly set in new field.That way review/burnin process could also reuse old files and recreate reviews/burnins.New version is produced during this process! + + + + +___ + +
+ + + +
+Feature: Keep synced hero representations up-to-date. (other ) - #4343 + +___ + +#### Brief description + +Keep previously synchronized sites up-to-date by comparing old and new sites and adding old sites if missing in new ones.Fix #4331 + + + + +___ + +
+ + + +
+Maya: Fix template builder bug where assets are not put in the right hierarchy (other ) - #4367 + +___ + +#### Brief description + +When buiding scene from template, the assets loaded from the placeholders are not put in the hierarchy. Plus, the assets are loaded in double. + + + + +___ + +
+ + + +
+Bump ua-parser-js from 0.7.31 to 0.7.33 in /website (other ) - #4371 + +___ + +Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33. +
+Changelog +

Sourced from ua-parser-js's changelog.

+
+

Version 0.7.31 / 1.0.2

+
    +
  • Fix OPPO Reno A5 incorrect detection
  • +
  • Fix TypeError Bug
  • +
  • Use AST to extract regexes and verify them with safe-regex
  • +
+

Version 0.7.32 / 1.0.32

+
    +
  • Add new browser : DuckDuckGo, Huawei Browser, LinkedIn
  • +
  • Add new OS : HarmonyOS
  • +
  • Add some Huawei models
  • +
  • Add Sharp Aquos TV
  • +
  • Improve detection Xiaomi Mi CC9
  • +
  • Fix Sony Xperia 1 III misidentified as Acer tablet
  • +
  • Fix Detect Sony BRAVIA as SmartTV
  • +
  • Fix Detect Xiaomi Mi TV as SmartTV
  • +
  • Fix Detect Galaxy Tab S8 as tablet
  • +
  • Fix WeGame mistakenly identified as WeChat
  • +
  • Fix included commas in Safari / Mobile Safari version
  • +
  • Increase UA_MAX_LENGTH to 350
  • +
+

Version 0.7.33 / 1.0.33

+
    +
  • Add new browser : Cobalt
  • +
  • Identify Macintosh as an Apple device
  • +
  • Fix ReDoS vulnerability
  • +
+

Version 0.8

+

Version 0.8 was created by accident. This version is now deprecated and no longer maintained, please update to version 0.7 / 1.0.

+
+
+
+Commits +
    +
  • f2d0db0 Bump version 0.7.33
  • +
  • a6140a1 Remove unsafe regex in trim() function
  • +
  • a886604 Fix #605 - Identify Macintosh as Apple device
  • +
  • b814bcd Merge pull request #606 from rileyjshaw/patch-1
  • +
  • 7f71024 Fix documentation
  • +
  • c239ac5 Merge pull request #604 from obecerra3/master
  • +
  • 8d3c2d3 Add new browser: Cobalt
  • +
  • d11fc47 Bump version 0.7.32
  • +
  • b490110 Merge branch 'develop' of github.com:faisalman/ua-parser-js
  • +
  • cb5da5e Merge pull request #600 from moekm/develop
  • +
  • Additional commits viewable in compare view
  • +
+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ua-parser-js&package-manager=npm_and_yarn&previous-version=0.7.31&new-version=0.7.33)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) +- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language +- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language +- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language +- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language + +You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ynput/OpenPype/network/alerts). + +
+___ + +
+ + + +
+Docs: Question about renaming in Kitsu (other ) - #4384 + +___ + +#### Brief description + +To keep memory of this discussion: https://discord.com/channels/517362899170230292/563751989075378201/1068112668491255818 + + + + +___ + +
+ + + +
+New Publisher: Fix Creator error typo (other ) - #4396 + +___ + +#### Brief description + +Fixes typo in error message. + + + + +___ + +
+ + + +
+Chore: pyproject.toml version because of Poetry (other ) - #4408 + +___ + +#### Brief description + +Automatization injects wrong format + + + + +___ + +
+ + + +
+Fix - remove minor part in toml (other ) - #4437 + +___ + +#### Brief description + +Causes issue in create_env and new Poetry + + + + +___ + +
+ + + +
+General: Add project code to anatomy (other ) - #4445 + +___ + +#### Brief description + +Added attribute `project_code` to `Anatomy` object. + + + +#### Description + +Anatomy already have access to almost all attributes from project anatomy except project code. This PR changing it. Technically `Anatomy` is everything what would be needed to get fill data of project. + +``` + +{ + + "project": { + + "name": anatomy.project_name, + + "code": anatomy.project_code + + } + +} + +``` + + +___ + +
+ + + +
+Maya: Arnold Scene Source overhaul - OP-4865 (other / maya ) - #4449 + +___ + +#### Brief description + +General overhaul of the Arnold Scene Source (ASS) workflow. + + + +#### Description + +This originally was to support static files (non-sequencial) ASS publishing, but digging deeper whole workflow needed an update to get ready for further issues. During this overhaul the following changes were made: + +- Generalized Arnold Standin workflow to a single loader. + +- Support multiple nodes as proxies. + +- Support proxies for `pointcache` family. + +- Generalized approach to proxies as resources, so they can be the same file format as the original.This workflow should allow further expansion to utilize operators and eventually USD. + + + + +___ + +
+ + + + ## [3.15.0](https://github.com/ynput/OpenPype/tree/3.15.0) [Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.10...3.15.0) diff --git a/pyproject.toml b/pyproject.toml index d1d5c8e2d3..2fc4f6fe39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,15 +114,15 @@ build-backend = "poetry.core.masonry.api" # https://pip.pypa.io/en/stable/cli/pip_install/#requirement-specifiers [openpype.qtbinding.windows] package = "PySide2" -version = "3.15.1" +version = "5.15.2" [openpype.qtbinding.darwin] package = "PySide6" -version = "3.15.1" +version = "6.4.1" [openpype.qtbinding.linux] package = "PySide2" -version = "3.15.1" +version = "5.15.2" # TODO: we will need to handle different linux flavours here and # also different macos versions too. From 7a2c94f9d511c2d15ca554709beded5fe3055515 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 11:06:37 +0100 Subject: [PATCH 234/281] limit groups query to 26 max --- openpype/hosts/tvpaint/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index 5e64773b8e..312a211d49 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -202,8 +202,9 @@ def get_groups_data(communicator=None): # Variable containing full path to output file "output_path = \"{}\"".format(output_filepath), "empty = 0", - # Loop over 100 groups - "FOR idx = 1 TO 100", + # Loop over 26 groups which is ATM maximum possible (in 11.7) + # - ref: https://www.tvpaint.com/forum/viewtopic.php?t=13880 + "FOR idx = 1 TO 26", # Receive information about groups "tv_layercolor \"getcolor\" 0 idx", "PARSE result clip_id group_index c_red c_green c_blue group_name", From c3c850f0348de62126689e887cc99b20cdbd538c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 11:06:53 +0100 Subject: [PATCH 235/281] change how groups are filtered for render passes --- .../tvpaint/plugins/create/create_render.py | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 41288e5968..d9355c42fd 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -219,15 +219,30 @@ class CreateRenderlayer(TVPaintCreator): f" was changed to \"{new_group_name}\"." )) - def get_pre_create_attr_defs(self): - groups_enum = [ - { - "label": group["name"], + def _get_groups_enum(self): + groups_enum = [] + empty_groups = [] + for group in get_groups_data(): + group_name = group["name"] + item = { + "label": group_name, "value": group["group_id"] } - for group in get_groups_data() - if group["name"] - ] + # TVPaint have defined how many color groups is available, but + # the count is not consistent across versions. It is not possible + # to know how many groups there is. + # + if group_name and group_name != "0": + if empty_groups: + groups_enum.extend(empty_groups) + empty_groups = [] + groups_enum.append(item) + else: + empty_groups.append(item) + return groups_enum + + def get_pre_create_attr_defs(self): + groups_enum = self._get_groups_enum() groups_enum.insert(0, {"label": "", "value": -1}) return [ @@ -249,14 +264,7 @@ class CreateRenderlayer(TVPaintCreator): ] def get_instance_attr_defs(self): - groups_enum = [ - { - "label": group["name"], - "value": group["group_id"] - } - for group in get_groups_data() - if group["name"] - ] + groups_enum = self._get_groups_enum() return [ EnumDef( "group_id", From 1b69c5e3409d3c67e384eef8c4c1c12054bed2cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 11:09:24 +0100 Subject: [PATCH 236/281] fix used key --- .../hosts/tvpaint/plugins/publish/validate_scene_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py index d235215ac9..4473e4b1b7 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py @@ -42,7 +42,7 @@ class ValidateProjectSettings(pyblish.api.ContextPlugin): "expected_width": expected_data["resolutionWidth"], "expected_height": expected_data["resolutionHeight"], "current_width": scene_data["resolutionWidth"], - "current_height": scene_data["resolutionWidth"], + "current_height": scene_data["resolutionHeight"], "expected_pixel_ratio": expected_data["pixelAspect"], "current_pixel_ratio": scene_data["pixelAspect"] } From 407068610afc447df075d555aa7b5e9068734f8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 13:58:20 +0100 Subject: [PATCH 237/281] return created instance in creators --- openpype/hosts/tvpaint/plugins/create/create_render.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d9355c42fd..71224927d1 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -195,13 +195,13 @@ class CreateRenderlayer(TVPaintCreator): new_group_name = pre_create_data.get("group_name") if not new_group_name or not group_id: - return + return new_instance self.log.debug("Changing name of the group.") new_group_name = pre_create_data.get("group_name") if not new_group_name or group_item["name"] == new_group_name: - return + return new_instance # Rename TVPaint group (keep color same) # - groups can't contain spaces rename_script = self.rename_script_template.format( @@ -218,6 +218,7 @@ class CreateRenderlayer(TVPaintCreator): f"Name of group with index {group_id}" f" was changed to \"{new_group_name}\"." )) + return new_instance def _get_groups_enum(self): groups_enum = [] @@ -497,6 +498,8 @@ class CreateRenderPass(TVPaintCreator): self._add_instance_to_context(new_instance) self._change_layers_group(selected_layers, group_id) + return new_instance + def _change_layers_group(self, layers, group_id): filtered_layers = [ layer From 7486591fab5e9ec0413262c5fdd80be774c6abab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 13:58:46 +0100 Subject: [PATCH 238/281] it is possible to pass layer names to create pass --- .../tvpaint/plugins/create/create_render.py | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 71224927d1..b324beb8f6 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -431,25 +431,40 @@ class CreateRenderPass(TVPaintCreator): self.log.debug("Checking selection.") # Get all selected layers and their group ids - selected_layers = [ - layer - for layer in layers_data - if layer["selected"] - ] + marked_layer_names = pre_create_data.get("layer_names") + if marked_layer_names is not None: + layers_by_name = {layer["name"]: layer for layer in layers_data} + marked_layers = [] + for layer_name in marked_layer_names: + layer = layers_by_name.get(layer_name) + if layer is None: + raise CreatorError( + f"Layer with name \"{layer_name}\" was not found") + marked_layers.append(layer) - # Raise if nothing is selected - if not selected_layers: - raise CreatorError("Nothing is selected. Please select layers.") + else: + marked_layers = [ + layer + for layer in layers_data + if layer["selected"] + ] + + # Raise if nothing is selected + if not marked_layers: + raise CreatorError("Nothing is selected. Please select layers.") + + marked_layer_names = {layer["name"] for layer in marked_layers} + + marked_layer_names = set(marked_layer_names) - selected_layer_names = {layer["name"] for layer in selected_layers} instances_to_remove = [] for instance in self.create_context.instances: if instance.creator_identifier != self.identifier: continue - layer_names = set(instance["layer_names"]) - if not layer_names.intersection(selected_layer_names): + cur_layer_names = set(instance["layer_names"]) + if not cur_layer_names.intersection(marked_layer_names): continue - new_layer_names = layer_names - selected_layer_names + new_layer_names = cur_layer_names - marked_layer_names if new_layer_names: instance["layer_names"] = list(new_layer_names) else: @@ -470,7 +485,7 @@ class CreateRenderPass(TVPaintCreator): self.log.info(f"New subset name is \"{label}\".") instance_data["label"] = label instance_data["group"] = f"{self.get_group_label()} ({render_layer})" - instance_data["layer_names"] = list(selected_layer_names) + instance_data["layer_names"] = list(marked_layer_names) if "creator_attributes" not in instance_data: instance_data["creator_attribtues"] = {} @@ -496,7 +511,7 @@ class CreateRenderPass(TVPaintCreator): self.host.write_instances(instances_data) self._add_instance_to_context(new_instance) - self._change_layers_group(selected_layers, group_id) + self._change_layers_group(marked_layers, group_id) return new_instance From 80a9c3e65ee6bd781f9906e115e58fa3fe46ff7f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 14:05:49 +0100 Subject: [PATCH 239/281] fix formatting --- openpype/hosts/tvpaint/plugins/create/create_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index b324beb8f6..cae894035a 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -451,7 +451,8 @@ class CreateRenderPass(TVPaintCreator): # Raise if nothing is selected if not marked_layers: - raise CreatorError("Nothing is selected. Please select layers.") + raise CreatorError( + "Nothing is selected. Please select layers.") marked_layer_names = {layer["name"] for layer in marked_layers} From 8002dc4f4fb937edb07336232285e48bfec59989 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Feb 2023 21:29:11 +0800 Subject: [PATCH 240/281] style fix --- openpype/hosts/max/api/lib.py | 38 ++- openpype/hosts/max/api/lib_renderproducts.py | 70 ++---- openpype/hosts/max/api/lib_rendersettings.py | 5 +- .../hosts/max/plugins/create/create_render.py | 2 +- .../max/plugins/publish/collect_render.py | 6 +- .../plugins/publish/submit_3dmax_deadline.py | 237 ------------------ .../plugins/publish/submit_max_deadline.py | 217 ++++++++++++++++ .../defaults/project_settings/deadline.json | 4 +- .../schema_project_deadline.json | 2 +- 9 files changed, 275 insertions(+), 306 deletions(-) delete mode 100644 openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py create mode 100644 openpype/modules/deadline/plugins/publish/submit_max_deadline.py diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ecea8b5541..14aa4d750a 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -134,19 +134,21 @@ def get_default_render_folder(project_setting=None): def set_framerange(startFrame, endFrame): - """Get/set the type of time range to be rendered. - - Possible values are: - - 1 -Single frame. - - 2 -Active time segment ( animationRange ). - - 3 -User specified Range. - - 4 -User specified Frame pickup string (for example "1,3,5-12"). """ - # hard-code, there should be a custom setting for this + Args: + start_frame (int): Start frame number. + end_frame (int): End frame number. + Note: + Frame range can be specified in different types. Possible values are: + + * `1` - Single frame. + * `2` - Active time segment ( animationRange ). + * `3` - User specified Range. + * `4` - User specified Frame pickup string (for example `1,3,5-12`). + + Todo: + Current type is hard-coded, there should be a custom setting for this. + """ rt.rendTimeType = 4 if startFrame is not None and endFrame is not None: frameRange = "{0}-{1}".format(startFrame, endFrame) @@ -157,3 +159,15 @@ def get_multipass_setting(project_setting=None): return (project_setting["max"] ["RenderSettings"] ["multipass"]) + +def get_max_version(): + """ + Args: + get max version date for deadline + + Returns: + #(25000, 62, 0, 25, 0, 0, 997, 2023, "") + max_info[7] = max version date + """ + max_info = rt.maxversion() + return max_info[7] diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index e09934e5de..00e0978bc8 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -15,7 +15,6 @@ from openpype.pipeline import legacy_io class RenderProducts(object): - @classmethod def __init__(self, project_settings=None): self._project_settings = project_settings if not self._project_settings: @@ -36,15 +35,11 @@ class RenderProducts(object): filename, container) - context = get_current_project_asset() - startFrame = context["data"].get("frameStart") - endFrame = context["data"].get("frameEnd") + 1 - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - full_render_list = self.beauty_render_product(output_file, - startFrame, - endFrame, - img_fmt) + full_render_list = [] + beauty = self.beauty_render_product(output_file, img_fmt) + full_render_list.append(beauty) + renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] @@ -60,41 +55,29 @@ class RenderProducts(object): renderer == "Quicksilver_Hardware_Renderer" ): render_elem_list = self.render_elements_product(output_file, - startFrame, - endFrame, img_fmt) - for render_elem in render_elem_list: - full_render_list.append(render_elem) + if render_elem_list: + for render_elem in render_elem_list: + full_render_list.append(render_elem) return full_render_list if renderer == "Arnold": aov_list = self.arnold_render_product(output_file, - startFrame, - endFrame, img_fmt) if aov_list: for aov in aov_list: full_render_list.append(aov) return full_render_list - def beauty_render_product(self, folder, startFrame, endFrame, fmt): - # get the beauty - beauty_frame_range = list() - - for f in range(startFrame, endFrame): - beauty = "{0}.{1}.{2}".format(folder, - str(f), - fmt) - beauty = beauty.replace("\\", "/") - beauty_frame_range.append(beauty) - - return beauty_frame_range - + def beauty_render_product(self, folder, fmt): + beauty_output = f"{folder}.####.{fmt}" + beauty_output = beauty_output.replace("\\", "/") + return beauty_output # TODO: Get the arnold render product - def arnold_render_product(self, folder, startFrame, endFrame, fmt): + def arnold_render_product(self, folder, fmt): """Get all the Arnold AOVs""" - aovs = list() + aovs = [] amw = rt.MaxtoAOps.AOVsManagerWindow() aov_mgr = rt.renderers.current.AOVManager @@ -105,21 +88,17 @@ class RenderProducts(object): for i in range(aov_group_num): # get the specific AOV group for aov in aov_mgr.drivers[i].aov_list: - for f in range(startFrame, endFrame): - render_element = "{0}_{1}.{2}.{3}".format(folder, - str(aov.name), - str(f), - fmt) - render_element = render_element.replace("\\", "/") - aovs.append(render_element) + render_element = f"{folder}_{aov.name}.####.{fmt}" + render_element = render_element.replace("\\", "/") + aovs.append(render_element) # close the AOVs manager window amw.close() return aovs - def render_elements_product(self, folder, startFrame, endFrame, fmt): + def render_elements_product(self, folder, fmt): """Get all the render element output files. """ - render_dirname = list() + render_dirname = [] render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() @@ -128,16 +107,11 @@ class RenderProducts(object): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") if renderlayer_name.enabled: - for f in range(startFrame, endFrame): - render_element = "{0}_{1}.{2}.{3}".format(folder, - renderpass, - str(f), - fmt) - render_element = render_element.replace("\\", "/") - render_dirname.append(render_element) + render_element = f"{folder}_{renderpass}.####.{fmt}" + render_element = render_element.replace("\\", "/") + render_dirname.append(render_element) return render_dirname def image_format(self): - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - return img_fmt + return self._project_settings["max"]["RenderSettings"]["image_format"] # noqa diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 92f716ba54..212c08846b 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -22,7 +22,6 @@ class RenderSettings(object): "underscore": "_" } - @classmethod def __init__(self, project_settings=None): self._project_settings = project_settings if not self._project_settings: @@ -41,7 +40,7 @@ class RenderSettings(object): if not found: raise RuntimeError("Camera not found") - def set_renderoutput(self, container): + def render_output(self, container): folder = rt.maxFilePath # hard-coded, should be customized in the setting file = rt.maxFileName @@ -144,7 +143,7 @@ class RenderSettings(object): aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) render_elem.SetRenderElementFileName(i, aov_name) - def get_renderoutput(self, container, output_dir): + def get_render_output(self, container, output_dir): output = os.path.join(output_dir, container) img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa outputFilename = "{0}..{1}".format(output, img_fmt) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 76c10ca4a9..269fff2e32 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -30,4 +30,4 @@ class CreateRender(plugin.MaxCreator): # set viewport camera for rendering(mandatory for deadline) RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) - RenderSettings().set_renderoutput(container_name) + RenderSettings().render_output(container_name) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 55391d40e8..16f8821986 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -4,7 +4,8 @@ import os import pyblish.api from pymxs import runtime as rt -from openpype.pipeline import legacy_io +from openpype.pipeline import get_current_asset_name +from openpype.hosts.max.api.lib import get_max_version from openpype.hosts.max.api.lib_renderproducts import RenderProducts from openpype.client import get_last_version_by_subset_name @@ -25,7 +26,7 @@ class CollectRender(pyblish.api.InstancePlugin): filepath = current_file.replace("\\", "/") context.data['currentFile'] = current_file - asset = legacy_io.Session["AVALON_ASSET"] + asset = get_current_asset_name() render_layer_files = RenderProducts().render_product(instance.name) folder = folder.replace("\\", "/") @@ -51,6 +52,7 @@ class CollectRender(pyblish.api.InstancePlugin): "subset": instance.name, "asset": asset, "publish": True, + "maxversion": str(get_max_version()), "imageFormat": imgFormat, "family": 'maxrender', "families": ['maxrender'], diff --git a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py b/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py deleted file mode 100644 index ed448abe1f..0000000000 --- a/openpype/modules/deadline/plugins/publish/submit_3dmax_deadline.py +++ /dev/null @@ -1,237 +0,0 @@ -import os -import json -import getpass - -import requests -import pyblish.api - -from openpype.pipeline import legacy_io -from openpype.settings import get_project_settings -from openpype.hosts.max.api.lib import ( - get_current_renderer, - get_multipass_setting -) -from openpype.hosts.max.api.lib_rendersettings import RenderSettings - - -class MaxSubmitRenderDeadline(pyblish.api.InstancePlugin): - """ - 3DMax File Submit Render Deadline - - """ - - label = "Submit 3DsMax Render to Deadline" - order = pyblish.api.IntegratorOrder - hosts = ["max"] - families = ["maxrender"] - targets = ["local"] - use_published = True - priority = 50 - chunk_size = 1 - group = None - deadline_pool = None - deadline_pool_secondary = None - framePerTask = 1 - - def process(self, instance): - context = instance.context - filepath = context.data["currentFile"] - filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "{0} - {1}".format(filename, instance.name) - - # StartFrame to EndFrame - frames = "{start}-{end}".format( - start=int(instance.data["frameStart"]), - end=int(instance.data["frameEnd"]) - ) - if self.use_published: - for item in context: - if "workfile" in item.data["families"]: - msg = "Workfile (scene) must be published along" - assert item.data["publish"] is True, msg - - template_data = item.data.get("anatomyData") - rep = item.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_data = context.data["anatomy"] - anatomy_filled = anatomy_data.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - filepath = os.path.normpath(template_filled) - filepath = filepath.replace("\\", "/") - self.log.info( - "Using published scene for render {}".format(filepath) - ) - if not os.path.exists(filepath): - self.log.error("published scene does not exist!") - - new_scene = self._clean_name(filepath) - # use the anatomy data for setting up the path of the files - orig_scene = self._clean_name(instance.context.data["currentFile"]) - expected_files = instance.data.get("expectedFiles") - - new_exp = [] - for file in expected_files: - new_file = str(file).replace(orig_scene, new_scene) - new_exp.append(new_file) - - instance.data["expectedFiles"] = new_exp - - metadata_folder = instance.data.get("publishRenderMetadataFolder") - if metadata_folder: - metadata_folder = metadata_folder.replace(orig_scene, - new_scene) - instance.data["publishRenderMetadataFolder"] = metadata_folder - - payload = { - "JobInfo": { - # Top-level group name - "BatchName": filename, - - # Job name, as seen in Monitor - "Name": jobname, - - # Arbitrary username, for visualisation in Monitor - "UserName": deadline_user, - - "Plugin": instance.data["plugin"], - "Group": self.group, - "Pool": self.deadline_pool, - "secondaryPool": self.deadline_pool_secondary, - "Frames": frames, - "ChunkSize": self.chunk_size, - "Priority": instance.data.get("priority", self.priority), - "Comment": comment, - "FramesPerTask": self.framePerTask - }, - "PluginInfo": { - # Input - "SceneFile": filepath, - "Version": "2023", - "SaveFile": True, - # Mandatory for Deadline - # Houdini version without patch number - - "IgnoreInputs": True - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - # Include critical environment variables with submission + api.Session - keys = [ - # Submit along the current Avalon tool setup that we launched - # this application with so the Render Slave can build its own - # similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9" - "AVALON_TOOLS", - "OPENPYPE_VERSION" - ] - # Add mongo url if it's enabled - if context.data.get("deadlinePassMongoUrl"): - keys.append("OPENPYPE_MONGO") - - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **legacy_io.Session) - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - # Include OutputFilename entries - # The first entry also enables double-click to preview rendered - # frames from Deadline Monitor - output_data = {} - # need to be fixed - for i, filepath in enumerate(instance.data["expectedFiles"]): - dirname = os.path.dirname(filepath) - fname = os.path.basename(filepath) - output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/") - output_data["OutputFilename%d" % i] = fname - - if not os.path.exists(dirname): - self.log.info("Ensuring output directory exists: %s" % - dirname) - os.makedirs(dirname) - - plugin_data = {} - project_setting = get_project_settings( - legacy_io.Session["AVALON_PROJECT"] - ) - - multipass = get_multipass_setting(project_setting) - if multipass: - plugin_data["DisableMultipass"] = 0 - else: - plugin_data["DisableMultipass"] = 1 - - if self.use_published: - old_output_dir = os.path.dirname(expected_files[0]) - output_beauty = RenderSettings().get_renderoutput(instance.name, - old_output_dir) - output_beauty = output_beauty.replace(orig_scene, new_scene) - output_beauty = output_beauty.replace("\\", "/") - plugin_data["RenderOutput"] = output_beauty - - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - if ( - renderer == "ART_Renderer" or - renderer == "Redshift_Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): - render_elem_list = RenderSettings().get_render_element() - for i, element in enumerate(render_elem_list): - element = element.replace(orig_scene, new_scene) - plugin_data["RenderElementOutputFilename%d" % i] = element # noqa - - self.log.debug("plugin data:{}".format(plugin_data)) - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) - - payload["JobInfo"].update(output_data) - payload["PluginInfo"].update(plugin_data) - - self.submit(instance, payload) - - def submit(self, instance, payload): - - context = instance.context - deadline_url = context.data.get("defaultDeadline") - deadline_url = instance.data.get( - "deadlineUrl", deadline_url) - - assert deadline_url, "Requires Deadline Webservice URL" - - plugin = payload["JobInfo"]["Plugin"] - self.log.info("Using Render Plugin : {}".format(plugin)) - - self.log.info("Submitting..") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - # Store output dir for unified publisher (expectedFilesequence) - expected_files = instance.data["expectedFiles"] - output_dir = os.path.dirname(expected_files[0]) - instance.data["toBeRenderedOn"] = "deadline" - instance.data["outputDir"] = output_dir - instance.data["deadlineSubmissionJob"] = response.json() - - def rename_render_element(self): - pass - - def _clean_name(self, path): - return os.path.splitext(os.path.basename(path))[0] diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py new file mode 100644 index 0000000000..3e00f8fd15 --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -0,0 +1,217 @@ +import os +import getpass +import copy + +import attr +from openpype.pipeline import legacy_io +from openpype.settings import get_project_settings +from openpype.hosts.max.api.lib import ( + get_current_renderer, + get_multipass_setting +) +from openpype.hosts.max.api.lib_rendersettings import RenderSettings +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo + + +@attr.s +class MaxPluginInfo(object): + SceneFile = attr.ib(default=None) # Input + Version = attr.ib(default=None) # Mandatory for Deadline + SaveFile = attr.ib(default=True) + IgnoreInputs = attr.ib(default=True) + + +class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): + + label = "Submit Render to Deadline" + hosts = ["max"] + families = ["maxrender"] + targets = ["local"] + + use_published = True + priority = 50 + tile_priority = 50 + chunk_size = 1 + jobInfo = {} + pluginInfo = {} + group = None + deadline_pool = None + deadline_pool_secondary = None + framePerTask = 1 + + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="3dsmax") + + # todo: test whether this works for existing production cases + # where custom jobInfo was stored in the project settings + job_info.update(self.jobInfo) + + instance = self._instance + context = instance.context + + # Always use the original work file name for the Job name even when + # rendering is done from the published Work File. The original work + # file name is clearer because it can also have subversion strings, + # etc. which are stripped for the published file. + src_filepath = context.data["currentFile"] + src_filename = os.path.basename(src_filepath) + + job_info.Name = "%s - %s" % (src_filename, instance.name) + job_info.BatchName = src_filename + job_info.Plugin = instance.data["plugin"] + job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) + + # Deadline requires integers in frame range + frames = "{start}-{end}".format( + start=int(instance.data["frameStart"]), + end=int(instance.data["frameEnd"]) + ) + job_info.Frames = frames + + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.ChunkSize = instance.data.get("chunkSize", 1) + job_info.Comment = context.data.get("comment") + job_info.Priority = instance.data.get("priority", self.priority) + job_info.FramesPerTask = instance.data.get("framesPerTask", 1) + + if self.group: + job_info.Group = self.group + + # Add options from RenderGlobals + render_globals = instance.data.get("renderGlobals", {}) + job_info.update(render_globals) + + keys = [ + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_VERSION", + "IS_TEST" + ] + # Add mongo url if it's enabled + if self._instance.context.data.get("deadlinePassMongoUrl"): + keys.append("OPENPYPE_MONGO") + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + for key in keys: + value = environment.get(key) + if not value: + continue + job_info.EnvironmentKeyValue[key] = value + + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" + job_info.EnvironmentKeyValue["OPENPYPE_LOG_NO_COLORS"] = "1" + + # Add list of expected files to job + # --------------------------------- + exp = instance.data.get("expectedFiles") + for filepath in exp: + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) + + return job_info + + def get_plugin_info(self): + instance = self._instance + + plugin_info = MaxPluginInfo( + SceneFile=self.scene_path, + Version=instance.data["maxversion"], + SaveFile = True, + IgnoreInputs = True + ) + + plugin_payload = attr.asdict(plugin_info) + + # Patching with pluginInfo from settings + for key, value in self.pluginInfo.items(): + plugin_payload[key] = value + + return plugin_payload + + def process_submission(self): + + instance = self._instance + context = instance.context + filepath = self.scene_path + + expected_files = instance.data["expectedFiles"] + if not expected_files: + raise RuntimeError("No Render Elements found!") + output_dir = os.path.dirname(expected_files[0]) + instance.data["outputDir"] = output_dir + instance.data["toBeRenderedOn"] = "deadline" + + filename = os.path.basename(filepath) + + payload_data = { + "filename": filename, + "dirname": output_dir + } + + self.log.debug("Submitting 3dsMax render..") + payload = self._use_puhlished_name(payload_data) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) + + def _use_puhlished_name(self, data): + instance = self._instance + job_info = copy.deepcopy(self.job_info) + plugin_info = copy.deepcopy(self.plugin_info) + plugin_data = {} + project_setting = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + + multipass = get_multipass_setting(project_setting) + if multipass: + plugin_data["DisableMultipass"] = 0 + else: + plugin_data["DisableMultipass"] = 1 + + expected_files = instance.data.get("expectedFiles") + if not expected_files: + raise RuntimeError("No render elements found") + old_output_dir = os.path.dirname(expected_files[0]) + output_beauty = RenderSettings().get_render_output(instance.name, + old_output_dir) + filepath = self.from_published_scene() + def _clean_name(path): + return os.path.splitext(os.path.basename(path))[0] + new_scene = _clean_name(filepath) + orig_scene = _clean_name(instance.context.data["currentFile"]) + + output_beauty = output_beauty.replace(orig_scene, new_scene) + output_beauty = output_beauty.replace("\\", "/") + plugin_data["RenderOutput"] = output_beauty + + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] + if ( + renderer == "ART_Renderer" or + renderer == "Redshift_Renderer" or + renderer == "V_Ray_6_Hotfix_3" or + renderer == "V_Ray_GPU_6_Hotfix_3" or + renderer == "Default_Scanline_Renderer" or + renderer == "Quicksilver_Hardware_Renderer" + ): + render_elem_list = RenderSettings().get_render_element() + for i, element in enumerate(render_elem_list): + element = element.replace(orig_scene, new_scene) + plugin_data["RenderElementOutputFilename%d" % i] = element # noqa + + self.log.debug("plugin data:{}".format(plugin_data)) + plugin_info.update(plugin_data) + + return job_info, plugin_info diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 7a5903d8e0..7183603c4b 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -36,7 +36,7 @@ "scene_patches": [], "strict_error_checking": true }, - "MaxSubmitRenderDeadline": { + "MaxSubmitDeadline": { "enabled": true, "optional": false, "active": true, @@ -122,4 +122,4 @@ } } } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 3d1b413d6c..a320dfca4f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -207,7 +207,7 @@ { "type": "dict", "collapsible": true, - "key": "MaxSubmitRenderDeadline", + "key": "MaxSubmitDeadline", "label": "3dsMax Submit to Deadline", "checkbox_key": "enabled", "children": [ From 3aa6e9ac9d78ce3404c39bb91ca8e367014cb6c8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Feb 2023 21:36:37 +0800 Subject: [PATCH 241/281] hound fix --- openpype/hosts/max/api/lib.py | 20 +++++++++---------- openpype/hosts/max/api/lib_renderproducts.py | 2 +- .../plugins/publish/submit_max_deadline.py | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 14aa4d750a..67e34c0a30 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -136,18 +136,17 @@ def get_default_render_folder(project_setting=None): def set_framerange(startFrame, endFrame): """ Args: - start_frame (int): Start frame number. - end_frame (int): End frame number. + start_frame (int): Start frame number. + end_frame (int): End frame number. Note: - Frame range can be specified in different types. Possible values are: + Frame range can be specified in different types. Possible values are: - * `1` - Single frame. - * `2` - Active time segment ( animationRange ). - * `3` - User specified Range. - * `4` - User specified Frame pickup string (for example `1,3,5-12`). - - Todo: - Current type is hard-coded, there should be a custom setting for this. + * `1` - Single frame. + * `2` - Active time segment ( animationRange ). + * `3` - User specified Range. + * `4`-User specified Frame pickup string (for example `1,3,5-12`). + TODO: + Current type is hard-coded, there should be a custom setting for this. """ rt.rendTimeType = 4 if startFrame is not None and endFrame is not None: @@ -160,6 +159,7 @@ def get_multipass_setting(project_setting=None): ["RenderSettings"] ["multipass"]) + def get_max_version(): """ Args: diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 00e0978bc8..6d476c9139 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -8,7 +8,6 @@ from openpype.hosts.max.api.lib import ( get_current_renderer, get_default_render_folder ) -from openpype.pipeline.context_tools import get_current_project_asset from openpype.settings import get_project_settings from openpype.pipeline import legacy_io @@ -74,6 +73,7 @@ class RenderProducts(object): beauty_output = f"{folder}.####.{fmt}" beauty_output = beauty_output.replace("\\", "/") return beauty_output + # TODO: Get the arnold render product def arnold_render_product(self, folder, fmt): """Get all the Arnold AOVs""" diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 3e00f8fd15..b53cc928d6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -128,8 +128,8 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info = MaxPluginInfo( SceneFile=self.scene_path, Version=instance.data["maxversion"], - SaveFile = True, - IgnoreInputs = True + SaveFile=True, + IgnoreInputs=True ) plugin_payload = attr.asdict(plugin_info) @@ -143,7 +143,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def process_submission(self): instance = self._instance - context = instance.context filepath = self.scene_path expected_files = instance.data["expectedFiles"] @@ -187,8 +186,10 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): output_beauty = RenderSettings().get_render_output(instance.name, old_output_dir) filepath = self.from_published_scene() + def _clean_name(path): return os.path.splitext(os.path.basename(path))[0] + new_scene = _clean_name(filepath) orig_scene = _clean_name(instance.context.data["currentFile"]) From db75941e00dfd9f503b0690cda7334ab71b0892d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Feb 2023 21:39:31 +0800 Subject: [PATCH 242/281] hound fix --- openpype/hosts/max/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 67e34c0a30..53e66c219e 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -144,7 +144,7 @@ def set_framerange(startFrame, endFrame): * `1` - Single frame. * `2` - Active time segment ( animationRange ). * `3` - User specified Range. - * `4`-User specified Frame pickup string (for example `1,3,5-12`). + * `4` - User specified Frame pickup string (for example `1,3,5-12`). TODO: Current type is hard-coded, there should be a custom setting for this. """ From 5e203ec6306da50cabca6efab09c4a679b451228 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 16:06:48 +0000 Subject: [PATCH 243/281] Only submit version when running from build. --- .../plugins/publish/submit_aftereffects_deadline.py | 7 ++++++- .../deadline/plugins/publish/submit_harmony_deadline.py | 9 +++++++-- .../plugins/publish/submit_houdini_remote_publish.py | 9 +++++++-- .../plugins/publish/submit_houdini_render_deadline.py | 9 +++++++-- .../deadline/plugins/publish/submit_maya_deadline.py | 9 +++++++-- .../publish/submit_maya_remote_publish_deadline.py | 9 +++++++-- .../deadline/plugins/publish/submit_nuke_deadline.py | 9 +++++++-- .../deadline/plugins/publish/submit_publish_job.py | 8 ++++++-- 8 files changed, 54 insertions(+), 15 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index f26047bb9d..83dd5b49e2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -12,6 +12,7 @@ from openpype.pipeline import legacy_io from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build @attr.s @@ -87,9 +88,13 @@ class AfterEffectsSubmitDeadline( "AVALON_APP_NAME", "OPENPYPE_DEV", "OPENPYPE_LOG_NO_COLORS", - "OPENPYPE_VERSION", "IS_TEST" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 425883393f..84fca11d9d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -14,6 +14,7 @@ from openpype.pipeline import legacy_io from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build class _ZipFile(ZipFile): @@ -279,10 +280,14 @@ class HarmonySubmitDeadline( "AVALON_TASK", "AVALON_APP_NAME", "OPENPYPE_DEV", - "OPENPYPE_LOG_NO_COLORS", - "OPENPYPE_VERSION", + "OPENPYPE_LOG_NO_COLORS" "IS_TEST" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py b/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py index 6a62f83cae..68aa653804 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py @@ -9,6 +9,7 @@ import pyblish.api from openpype.pipeline import legacy_io from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin): @@ -133,9 +134,13 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin): # Submit along the current Avalon tool setup that we launched # this application with so the Render Slave can build its own # similar environment using it, e.g. "houdini17.5;pluginx2.3" - "AVALON_TOOLS", - "OPENPYPE_VERSION" + "AVALON_TOOLS" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 2b17b644b8..73ab689c9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -10,6 +10,7 @@ import pyblish.api from openpype.pipeline import legacy_io from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): @@ -105,9 +106,13 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): # Submit along the current Avalon tool setup that we launched # this application with so the Render Slave can build its own # similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9" - "AVALON_TOOLS", - "OPENPYPE_VERSION" + "AVALON_TOOLS" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index ed37ff1897..22b5c02296 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -38,6 +38,7 @@ from openpype.hosts.maya.api.lib import get_attr_in_layer from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build def _validate_deadline_bool_value(instance, attribute, value): @@ -165,10 +166,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "OPENPYPE_DEV", - "OPENPYPE_VERSION", + "OPENPYPE_DEV" "IS_TEST" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index bab6591c7f..25f859554f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -7,6 +7,7 @@ from maya import cmds from openpype.pipeline import legacy_io, PublishXmlValidationError from openpype.settings import get_project_settings from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build import pyblish.api @@ -104,9 +105,13 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): keys = [ "FTRACK_API_USER", "FTRACK_API_KEY", - "FTRACK_SERVER", - "OPENPYPE_VERSION" + "FTRACK_SERVER" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index d1948d8d50..cca2a4d896 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -10,6 +10,7 @@ import pyblish.api import nuke from openpype.pipeline import legacy_io from openpype.tests.lib import is_in_tests +from openpype.lib import is_running_from_build class NukeSubmitDeadline(pyblish.api.InstancePlugin): @@ -265,9 +266,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "PYBLISHPLUGINPATH", "NUKE_PATH", "TOOL_ENV", - "FOUNDRY_LICENSE", - "OPENPYPE_VERSION" + "FOUNDRY_LICENSE" ] + + # Add OpenPype version if we are running from build. + if is_running_from_build(): + keys.append("OPENPYPE_VERSION") + # Add mongo url if it's enabled if instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 8f8f4246dd..e132d7323b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -20,6 +20,7 @@ from openpype.pipeline import ( ) from openpype.tests.lib import is_in_tests from openpype.pipeline.farm.patterning import match_aov_pattern +from openpype.lib import is_running_from_build def get_resources(project_name, version, extension=None): @@ -136,10 +137,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_SERVER", "AVALON_APP_NAME", - "OPENPYPE_USERNAME", - "OPENPYPE_VERSION" + "OPENPYPE_USERNAME" ] + # Add OpenPype version if we are running from build. + if is_running_from_build(): + environ_keys.append("OPENPYPE_VERSION") + # custom deadline attributes deadline_department = "" deadline_pool = "" From 72134d4a211ee325799c439a31fded53c41b04a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Feb 2023 18:33:15 +0100 Subject: [PATCH 244/281] Fix changed location of reset_frame_range Location in commands caused cyclic import --- openpype/hosts/maya/api/commands.py | 49 ------------------- openpype/hosts/maya/api/lib.py | 49 ++++++++++++++++++- openpype/hosts/maya/api/lib_rendersettings.py | 2 +- openpype/hosts/maya/api/menu.py | 3 +- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index 19ad18d824..018340d86c 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -4,7 +4,6 @@ from maya import cmds from openpype.client import get_asset_by_name, get_project from openpype.pipeline import legacy_io -from . import lib class ToolWindows: @@ -58,54 +57,6 @@ def edit_shader_definitions(): window.show() -def reset_frame_range(): - """Set frame range to current asset""" - - fps = lib.convert_to_maya_fps( - float(legacy_io.Session.get("AVALON_FPS", 25)) - ) - lib.set_scene_fps(fps) - - # Set frame start/end - project_name = legacy_io.active_project() - asset_name = legacy_io.Session["AVALON_ASSET"] - asset = get_asset_by_name(project_name, asset_name) - - frame_start = asset["data"].get("frameStart") - frame_end = asset["data"].get("frameEnd") - # Backwards compatibility - if frame_start is None or frame_end is None: - frame_start = asset["data"].get("edit_in") - frame_end = asset["data"].get("edit_out") - - if frame_start is None or frame_end is None: - cmds.warning("No edit information found for %s" % asset_name) - return - - handles = asset["data"].get("handles") or 0 - handle_start = asset["data"].get("handleStart") - if handle_start is None: - handle_start = handles - - handle_end = asset["data"].get("handleEnd") - if handle_end is None: - handle_end = handles - - frame_start -= int(handle_start) - frame_end += int(handle_end) - - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) - cmds.playbackOptions(animationStartTime=frame_start) - cmds.playbackOptions(animationEndTime=frame_end) - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) - cmds.currentTime(frame_start) - - cmds.setAttr("defaultRenderGlobals.startFrame", frame_start) - cmds.setAttr("defaultRenderGlobals.endFrame", frame_end) - - def _resolution_from_document(doc): if not doc or "data" not in doc: print("Entered document is not valid. \"{}\"".format(str(doc))) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b920428b20..84c6e6929d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -34,7 +34,6 @@ from openpype.pipeline import ( registered_host, ) from openpype.pipeline.context_tools import get_current_project_asset -from .commands import reset_frame_range self = sys.modules[__name__] @@ -2065,6 +2064,54 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) +def reset_frame_range(): + """Set frame range to current asset""" + + fps = convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + set_scene_fps(fps) + + # Set frame start/end + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + asset = get_asset_by_name(project_name, asset_name) + + frame_start = asset["data"].get("frameStart") + frame_end = asset["data"].get("frameEnd") + # Backwards compatibility + if frame_start is None or frame_end is None: + frame_start = asset["data"].get("edit_in") + frame_end = asset["data"].get("edit_out") + + if frame_start is None or frame_end is None: + cmds.warning("No edit information found for %s" % asset_name) + return + + handles = asset["data"].get("handles") or 0 + handle_start = asset["data"].get("handleStart") + if handle_start is None: + handle_start = handles + + handle_end = asset["data"].get("handleEnd") + if handle_end is None: + handle_end = handles + + frame_start -= int(handle_start) + frame_end += int(handle_end) + + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.playbackOptions(animationStartTime=frame_start) + cmds.playbackOptions(animationEndTime=frame_end) + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.currentTime(frame_start) + + cmds.setAttr("defaultRenderGlobals.startFrame", frame_start) + cmds.setAttr("defaultRenderGlobals.endFrame", frame_end) + + def reset_scene_resolution(): """Apply the scene resolution from the project definition diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 6190a49401..2a730100de 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -14,7 +14,7 @@ from openpype.settings import ( from openpype.pipeline import legacy_io from openpype.pipeline import CreatorError from openpype.pipeline.context_tools import get_current_project_asset -from openpype.hosts.maya.api.commands import reset_frame_range +from openpype.hosts.maya.api.lib import reset_frame_range class RenderSettings(object): diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 791475173f..0f48a133a6 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -12,7 +12,6 @@ from openpype.pipeline.workfile import BuildWorkfile from openpype.tools.utils import host_tools from openpype.hosts.maya.api import lib, lib_rendersettings from .lib import get_main_window, IS_HEADLESS -from .commands import reset_frame_range from .workfile_template_builder import ( create_placeholder, @@ -113,7 +112,7 @@ def install(): cmds.menuItem( "Reset Frame Range", - command=lambda *args: reset_frame_range() + command=lambda *args: lib.reset_frame_range() ) cmds.menuItem( From ffcc1656d2a4641bf9ae36e4a28a0c831f8d8a4f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 21 Feb 2023 11:08:07 +0800 Subject: [PATCH 245/281] cosmetic issue fix --- openpype/hosts/max/api/lib.py | 13 +++---- openpype/hosts/max/api/lib_renderproducts.py | 23 +++++------ openpype/hosts/max/api/lib_rendersettings.py | 39 +++++++++++-------- .../max/plugins/publish/collect_render.py | 2 +- .../plugins/publish/submit_max_deadline.py | 20 +++++----- 5 files changed, 48 insertions(+), 49 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 53e66c219e..6ee934d3da 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -135,17 +135,14 @@ def get_default_render_folder(project_setting=None): def set_framerange(startFrame, endFrame): """ - Args: - start_frame (int): Start frame number. - end_frame (int): End frame number. Note: Frame range can be specified in different types. Possible values are: + * `1` - Single frame. + * `2` - Active time segment ( animationRange ). + * `3` - User specified Range. + * `4` - User specified Frame pickup string (for example `1,3,5-12`). - * `1` - Single frame. - * `2` - Active time segment ( animationRange ). - * `3` - User specified Range. - * `4` - User specified Frame pickup string (for example `1,3,5-12`). - TODO: + Todo: Current type is hard-coded, there should be a custom setting for this. """ rt.rendTimeType = 4 diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 6d476c9139..a74a6a7426 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -45,28 +45,25 @@ class RenderProducts(object): if renderer == "VUE_File_Renderer": return full_render_list - if ( - renderer == "ART_Renderer" or - renderer == "Redshift_Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): + if renderer in [ + "ART_Renderer", + "Redshift_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: render_elem_list = self.render_elements_product(output_file, img_fmt) if render_elem_list: - for render_elem in render_elem_list: - full_render_list.append(render_elem) - + full_render_list.extend(iter(render_elem_list)) return full_render_list if renderer == "Arnold": aov_list = self.arnold_render_product(output_file, img_fmt) if aov_list: - for aov in aov_list: - full_render_list.append(aov) + full_render_list.extend(iter(aov_list)) return full_render_list def beauty_render_product(self, folder, fmt): diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 212c08846b..94c6ee775b 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -23,6 +23,11 @@ class RenderSettings(object): } def __init__(self, project_settings=None): + """ + Set up the naming convention for the render + elements for the deadline submission + """ + self._project_settings = project_settings if not self._project_settings: self._project_settings = get_project_settings( @@ -61,9 +66,9 @@ class RenderSettings(object): width = context["data"].get("resolutionWidth") height = context["data"].get("resolutionHeight") # Set Frame Range - startFrame = context["data"].get("frameStart") - endFrame = context["data"].get("frameEnd") - set_framerange(startFrame, endFrame) + frame_start = context["data"].get("frame_start") + frame_end = context["data"].get("frame_end") + set_framerange(frame_start, frame_end) # get the production render renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] @@ -78,24 +83,24 @@ class RenderSettings(object): )] except KeyError: aov_separator = "." - outputFilename = "{0}..{1}".format(output, img_fmt) - outputFilename = outputFilename.replace("{aov_separator}", + output_filename = "{0}..{1}".format(output, img_fmt) + output_filename = output_filename.replace("{aov_separator}", aov_separator) - rt.rendOutputFilename = outputFilename + rt.rendOutputFilename = output_filename if renderer == "VUE_File_Renderer": return # TODO: Finish the arnold render setup if renderer == "Arnold": self.arnold_setup() - if ( - renderer == "ART_Renderer" or - renderer == "Redshift_Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): + if renderer in [ + "ART_Renderer", + "Redshift_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: self.render_element_layer(output, width, height, img_fmt) rt.rendSaveFile = True @@ -146,11 +151,11 @@ class RenderSettings(object): def get_render_output(self, container, output_dir): output = os.path.join(output_dir, container) img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - outputFilename = "{0}..{1}".format(output, img_fmt) - return outputFilename + output_filename = "{0}..{1}".format(output, img_fmt) + return output_filename def get_render_element(self): - orig_render_elem = list() + orig_render_elem = [] render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() if render_elem_num < 0: diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 16f8821986..7656c641ed 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -14,7 +14,7 @@ class CollectRender(pyblish.api.InstancePlugin): """Collect Render for Deadline""" order = pyblish.api.CollectorOrder + 0.01 - label = "Collect 3dmax Render Layers" + label = "Collect 3dsmax Render Layers" hosts = ['max'] families = ["maxrender"] diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index b53cc928d6..417a03de74 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -160,11 +160,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): } self.log.debug("Submitting 3dsMax render..") - payload = self._use_puhlished_name(payload_data) + payload = self._use_published_name(payload_data) job_info, plugin_info = payload self.submit(self.assemble_payload(job_info, plugin_info)) - def _use_puhlished_name(self, data): + def _use_published_name(self, data): instance = self._instance job_info = copy.deepcopy(self.job_info) plugin_info = copy.deepcopy(self.plugin_info) @@ -199,14 +199,14 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] - if ( - renderer == "ART_Renderer" or - renderer == "Redshift_Renderer" or - renderer == "V_Ray_6_Hotfix_3" or - renderer == "V_Ray_GPU_6_Hotfix_3" or - renderer == "Default_Scanline_Renderer" or - renderer == "Quicksilver_Hardware_Renderer" - ): + if renderer in [ + "ART_Renderer", + "Redshift_Renderer", + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3", + "Default_Scanline_Renderer", + "Quicksilver_Hardware_Renderer", + ]: render_elem_list = RenderSettings().get_render_element() for i, element in enumerate(render_elem_list): element = element.replace(orig_scene, new_scene) From 8ca7a719046bc2ceb2075db20a959ccc88797447 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 21 Feb 2023 11:10:02 +0800 Subject: [PATCH 246/281] hound fix --- openpype/hosts/max/api/lib.py | 6 +++--- openpype/hosts/max/api/lib_rendersettings.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 6ee934d3da..3383330792 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -137,9 +137,9 @@ def set_framerange(startFrame, endFrame): """ Note: Frame range can be specified in different types. Possible values are: - * `1` - Single frame. - * `2` - Active time segment ( animationRange ). - * `3` - User specified Range. + * `1` - Single frame. + * `2` - Active time segment ( animationRange ). + * `3` - User specified Range. * `4` - User specified Frame pickup string (for example `1,3,5-12`). Todo: diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 94c6ee775b..b07d19f176 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -85,7 +85,7 @@ class RenderSettings(object): aov_separator = "." output_filename = "{0}..{1}".format(output, img_fmt) output_filename = output_filename.replace("{aov_separator}", - aov_separator) + aov_separator) rt.rendOutputFilename = output_filename if renderer == "VUE_File_Renderer": return From 373b1abffc44a2be38643d28131ee0abefe3c52c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 21 Feb 2023 07:21:30 +0000 Subject: [PATCH 247/281] Ensure viewport options work ahead of playblasting. --- .../maya/plugins/publish/extract_playblast.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 1f9f9db99a..fe7e83a84b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -119,6 +119,24 @@ class ExtractPlayblast(publish.Extractor): pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + # Need to explicitly enable some viewport changes so the viewport is + # refreshed ahead of playblasting. + panel = cmds.getPanel(withFocus=True) + keys = [ + "useDefaultMaterial", + "wireframeOnShaded", + "xray", + "jointXray", + "backfaceCulling" + ] + viewport_defaults = {} + for key in keys: + viewport_defaults[key] = cmds.modelEditor( + panel, query=True, **{key: True} + ) + if preset["viewport_options"][key]: + cmds.modelEditor(panel, edit=True, **{key: True}) + with lib.maintained_time(): filename = preset.get("filename", "%TEMP%") @@ -139,6 +157,10 @@ class ExtractPlayblast(publish.Extractor): path = capture.capture(log=self.log, **preset) + # Restoring viewport options. + for key, value in viewport_defaults.items(): + cmds.modelEditor(panel, edit=True, **{key: value}) + cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) self.log.debug("playblast path {}".format(path)) From 38c9330caef225e4ab632f5834c92fff5a3ea8b9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 10:41:48 +0100 Subject: [PATCH 248/281] returning nightly builds --- .github/workflows/nightly_merge.yml | 29 +++++++++++++ .github/workflows/prerelease.yml | 67 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 .github/workflows/nightly_merge.yml create mode 100644 .github/workflows/prerelease.yml diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml new file mode 100644 index 0000000000..1776d7a464 --- /dev/null +++ b/.github/workflows/nightly_merge.yml @@ -0,0 +1,29 @@ +name: Dev -> Main + +on: + schedule: + - cron: '21 3 * * 3,6' + workflow_dispatch: + +jobs: + develop-to-main: + + runs-on: ubuntu-latest + + steps: + - name: 🚛 Checkout Code + uses: actions/checkout@v2 + + - name: 🔨 Merge develop to main + uses: everlytic/branch-merge@1.1.0 + with: + github_token: ${{ secrets.YNPUT_BOT_TOKEN }} + source_ref: 'develop' + target_branch: 'main' + commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' + + - name: Invoke pre-release workflow + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: Nightly Prerelease + token: ${{ secrets.YNPUT_BOT_TOKEN }} diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000000..571b0339e1 --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,67 @@ +name: Nightly Prerelease + +on: + workflow_dispatch: + + +jobs: + create_nightly: + runs-on: ubuntu-latest + + steps: + - name: 🚛 Checkout Code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install Python requirements + run: pip install gitpython semver PyGithub + + - name: 🔎 Determine next version type + id: version_type + run: | + TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) + echo "type=${TYPE}" >> $GITHUB_OUTPUT + + - name: 💉 Inject new version into files + id: version + if: steps.version_type.outputs.type != 'skip' + run: | + NEW_VERSION_TAG=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) + echo "next_tag=${NEW_VERSION_TAG}" >> $GITHUB_OUTPUT + + - name: 💾 Commit and Tag + id: git_commit + if: steps.version_type.outputs.type != 'skip' + run: | + git config user.email ${{ secrets.CI_EMAIL }} + git config user.name ${{ secrets.CI_USER }} + git checkout main + git pull + git add . + git commit -m "[Automated] Bump version" + tag_name="CI/${{ steps.version.outputs.next_tag }}" + echo $tag_name + git tag -a $tag_name -m "nightly build" + + - name: Push to protected main branch + uses: CasperWA/push-protected@v2.10.0 + with: + token: ${{ secrets.YNPUT_BOT_TOKEN }} + branch: main + tags: true + unprotect_reviews: true + + - name: 🔨 Merge main back to develop + uses: everlytic/branch-merge@1.1.0 + if: steps.version_type.outputs.type != 'skip' + with: + github_token: ${{ secrets.YNPUT_BOT_TOKEN }} + source_ref: 'main' + target_branch: 'develop' + commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' From 2894cef94c15b35f0ffff4676278a422d80a0ff6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Feb 2023 15:11:48 +0100 Subject: [PATCH 249/281] rename color group by render layer variant --- .../tvpaint/plugins/create/create_render.py | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index cae894035a..841489e14b 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -145,6 +145,7 @@ class CreateRenderlayer(TVPaintCreator): def create(self, subset_name, instance_data, pre_create_data): self.log.debug("Query data from workfile.") + group_name = instance_data["variant"] group_id = pre_create_data.get("group_id") # This creator should run only on one group if group_id is None or group_id == -1: @@ -193,15 +194,10 @@ class CreateRenderlayer(TVPaintCreator): ) self._store_new_instance(new_instance) - new_group_name = pre_create_data.get("group_name") - if not new_group_name or not group_id: + if not group_id or group_item["name"] == group_name: return new_instance self.log.debug("Changing name of the group.") - - new_group_name = pre_create_data.get("group_name") - if not new_group_name or group_item["name"] == new_group_name: - return new_instance # Rename TVPaint group (keep color same) # - groups can't contain spaces rename_script = self.rename_script_template.format( @@ -210,13 +206,13 @@ class CreateRenderlayer(TVPaintCreator): r=group_item["red"], g=group_item["green"], b=group_item["blue"], - name=new_group_name + name=group_name ) execute_george_through_file(rename_script) self.log.info(( f"Name of group with index {group_id}" - f" was changed to \"{new_group_name}\"." + f" was changed to \"{group_name}\"." )) return new_instance @@ -252,11 +248,6 @@ class CreateRenderlayer(TVPaintCreator): label="Group", items=groups_enum ), - TextDef( - "group_name", - label="New group name", - placeholder="< Keep unchanged >" - ), BoolDef( "mark_for_review", label="Review", @@ -280,10 +271,44 @@ class CreateRenderlayer(TVPaintCreator): ] def update_instances(self, update_list): + self._update_color_groups() self._update_renderpass_groups() super().update_instances(update_list) + def _update_color_groups(self): + render_layer_instances = [] + for instance in self.create_context.instances: + if instance.creator_identifier == self.identifier: + render_layer_instances.append(instance) + + if not render_layer_instances: + return + + groups_by_id = { + group["group_id"]: group + for group in get_groups_data() + } + grg_script_lines = [] + for instance in render_layer_instances: + group_id = instance["creator_attributes"]["group_id"] + variant = instance["variant"] + group = groups_by_id[group_id] + if group["name"] == variant: + continue + + grg_script_lines.append(self.rename_script_template.format( + clip_id=group["clip_id"], + group_id=group["group_id"], + r=group["red"], + g=group["green"], + b=group["blue"], + name=variant + )) + + if grg_script_lines: + execute_george_through_file("\n".join(grg_script_lines)) + def _update_renderpass_groups(self): render_layer_instances = {} render_pass_instances = collections.defaultdict(list) From 1d0952014314217b022dffea7340db8776041a54 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 21 Feb 2023 15:18:07 +0000 Subject: [PATCH 250/281] Update openpype/hosts/maya/plugins/publish/extract_playblast.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index f85772886b..1966ad7b66 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -163,8 +163,7 @@ class ExtractPlayblast(publish.Extractor): path = capture.capture(log=self.log, **preset) # Restoring viewport options. - for key, value in viewport_defaults.items(): - cmds.modelEditor(panel, edit=True, **{key: value}) + cmds.modelEditor(panel, edit=True, **viewport_defaults) cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) From f26b44a2aadebc60ac92ae1a6de81e9442dab429 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Feb 2023 16:19:30 +0100 Subject: [PATCH 251/281] fix 'creator_attributes' key --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 841489e14b..6a857676a5 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -513,9 +513,9 @@ class CreateRenderPass(TVPaintCreator): instance_data["group"] = f"{self.get_group_label()} ({render_layer})" instance_data["layer_names"] = list(marked_layer_names) if "creator_attributes" not in instance_data: - instance_data["creator_attribtues"] = {} + instance_data["creator_attributes"] = {} - creator_attributes = instance_data["creator_attribtues"] + creator_attributes = instance_data["creator_attributes"] mark_for_review = pre_create_data.get("mark_for_review") if mark_for_review is None: mark_for_review = self.mark_for_review From 08e2d36f199b617a35120b73fd9f0de2b429fa60 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Feb 2023 16:29:25 +0100 Subject: [PATCH 252/281] ignore missing layers in unrelated validators --- .../plugins/publish/validate_duplicated_layer_names.py | 3 +++ .../tvpaint/plugins/publish/validate_layers_visibility.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py b/openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py index 9f61bdbcd0..722d76b4d2 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py @@ -20,6 +20,9 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin): duplicated_layer_names = [] for layer_name in layer_names: layers = layers_by_name.get(layer_name) + # It is not job of this validator to handle missing layers + if layers is None: + continue if len(layers) > 1: duplicated_layer_names.append(layer_name) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py index 47632453fc..6a496a2e49 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py @@ -11,8 +11,13 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin): families = ["review", "render"] def process(self, instance): + layers = instance.data["layers"] + # Instance have empty layers + # - it is not job of this validator to check that + if not layers: + return layer_names = set() - for layer in instance.data["layers"]: + for layer in layers: layer_names.add(layer["name"]) if layer["visible"]: return From bd3207f2b6a317e0d77e6eaaa19dc60390f76220 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Feb 2023 17:50:47 +0100 Subject: [PATCH 253/281] Modify artist docstrings to contain Publisher related information and images --- website/docs/artist_hosts_tvpaint.md | 154 ++++++++++------------- website/docs/assets/tvp_create_layer.png | Bin 29058 -> 174149 bytes website/docs/assets/tvp_create_pass.png | Bin 36545 -> 132716 bytes 3 files changed, 68 insertions(+), 86 deletions(-) diff --git a/website/docs/artist_hosts_tvpaint.md b/website/docs/artist_hosts_tvpaint.md index a0ce5d5ff8..baa8c0a09d 100644 --- a/website/docs/artist_hosts_tvpaint.md +++ b/website/docs/artist_hosts_tvpaint.md @@ -43,13 +43,55 @@ You can start your work. ## Usage In TVPaint you can find the Tools in OpenPype menu extension. The OpenPype Tools menu should be available in your work area. However, sometimes it happens that the Tools menu is hidden. You can display the extension panel by going to `Windows -> Plugins -> OpenPype`. +## Create & Publish +As you might already know, to be able to publish, you have to mark what should be published. The marking part is called **Create**. In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Layers](#render-layer)** and **[Render Passes](#render-pass)**. -## Create -In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Passes](#render-pass)** and **[Render Layers](#render-layer)**. +:::important +TVPaint integration tries to not guess what you want to publish from the scene. Therefore, you should tell what you want to publish. +::: -You have the possibility to organize your layers by using `Color group`. +![createlayer](assets/tvp_publisher.png) -On the bottom left corner of your timeline, you will note a `Color group` button. +### Review +`Review` will render all visible layers and create a reviewable output. +- Is automatically created without any manual work. +- You can disable the created instance if you want to skip review. + +### Workfile +`Workfile` integrate the source TVPaint file during publishing. Publishing of workfile is useful for backups. +- Is automatically created without any manual work. +- You can disable the created instance if you want to skip review. + +### Render Layer + +
+
+ +Render Layer bakes all the animation layers of one particular color group together. + +- In the **Create** tab, pick `Render Layer` +- Fill `variant`, type in the name that the final published RenderLayer should have according to the naming convention in your studio. *(L10, BG, Hero, etc.)* + - Color group will be renamed to the **variant** value +- Choose color group from combobox + - or select a layer of a particular color and set combobox to **<Use selection>** +- Hit `Create` button + +You have just created Render Layer. Now choose any amount of animation layers that need to be rendered together and assign them the color group. + +You can change `variant` later in **Publish** tab. + +
+
+ +![createlayer](assets/tvp_create_layer.png) + +
+
+
+ +**How to mark TVPaint layer to a group** + +In the bottom left corner of your timeline, you will note a **Color group** button. ![colorgroups](assets/tvp_color_groups.png) @@ -61,63 +103,29 @@ The timeline's animation layer can be marked by the color you pick from your Col ![timeline](assets/tvp_timeline_color.png) -:::important -OpenPype specifically never tries to guess what you want to publish from the scene. Therefore, you have to tell OpenPype what you want to publish. There are three ways how to publish render from the scene. -::: - -When you want to publish `review` or `render layer` or `render pass`, open the `Creator` through the Tools menu `Create` button. - -### Review -`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack. -- Is automatically created during publishing. - -### Workfile -`Workfile` stores the source workfile as is during publishing (e.g. for backup). -- Is automatically created during publishing. - -### Render Layer - -
-
- - -Render Layer bakes all the animation layers of one particular color group together. - -- Choose any amount of animation layers that need to be rendered together and assign them a color group. -- Select any layer of a particular color -- Go to `Creator` and choose `RenderLayer`. -- In the `Subset`, type in the name that the final published RenderLayer should have according to the naming convention in your studio. *(L10, BG, Hero, etc.)* -- Press `Create` -- When you run [publish](#publish), the whole color group will be rendered together and published as a single `RenderLayer` - -
-
- -![createlayer](assets/tvp_create_layer.png) - -
-
- - - - ### Render Pass -Render Passes are smaller individual elements of a Render Layer. A `character` render layer might +Render Passes are smaller individual elements of a [Render Layer](artist_hosts_tvpaint.md#render-layer). A `character` render layer might consist of multiple render passes such as `Line`, `Color` and `Shadow`. +Render Passes are specific because they have to belong to a particular Render Layer. You have to select to which Render Layer the pass belongs. Try to refresh if you don't see demanded Render Layer in the options.
-Render Passes are specific because they have to belong to a particular layer. If you try to create a render pass and did not create any render layers before, an error message will pop up. -When you want to create `RenderPass` -- choose one or several animation layers within one color group that you want to publish -- In the Creator, pick `RenderPass` -- Fill the `Subset` with the name of your pass, e.g. `Color`. +When you want to create Render Pass +- choose one or several TVPaint layers +- In the **Create** tab, pick `Render Pass` +- Fill the `variant` with desired name of pass, e.g. `Color`. +- Select Render Layer to which belongs in Render Layer combobox + - If you don't see new Render Layer try refresh first - Press `Create` +You have just created Render Pass. Selected TVPaint layers should be marked with color group of Render Layer. + +You can change `variant` or Render Layer later in **Publish** tab. +
@@ -126,48 +134,22 @@ When you want to create `RenderPass`
+:::warning +You cannot change TVPaint layer name once you mark it as part of Render Pass. You would have to remove created Render Pass and create it again with new TVPaint layer name. +::: +

In this example, OpenPype will render selected animation layers within the given color group. E.i. the layers *L020_colour_fx*, *L020_colour_mouth*, and *L020_colour_eye* will be rendered as one pass belonging to the yellow RenderLayer. ![renderpass](assets/tvp_timeline_color2.png) - -:::note -You can check your RendrePasses and RenderLayers in [Subset Manager](#subset-manager) or you can start publishing. The publisher will show you a collection of all instances on the left side. -::: - - ---- - -## Publish - -
-
- -Now that you have created the required instances, you can publish them via `Publish` tool. -- Click on `Publish` in OpenPype Tools menu. -- wait until all instances are collected. -- You can check on the left side whether all your instances have been created and are ready for publishing. -- Fill the comment on the bottom of the window. -- Press the `Play` button to publish - -
-
- -![pyblish](assets/tvp_pyblish_render.png) - -
-
- -Once the `Publisher` turns gets green your renders have been published. - ---- - -## Subset Manager -All created instances (render layers, passes, and reviews) will be shown as a simple list. If you don't want to publish some, right click on the item in the list and select `Remove instance`. - -![subsetmanager](assets/tvp_subset_manager.png) +Now that you have created the required instances, you can publish them. +- Fill the comment on the bottom of the window +- Double check enabled instance and their context +- Press `Publish` +- Wait to finish +- Once the `Publisher` turns gets green your renders have been published. --- diff --git a/website/docs/assets/tvp_create_layer.png b/website/docs/assets/tvp_create_layer.png index 9d243da17a099cae576552193711c2898e76f8e9..25081bdf46ace8070b001c9b87dddd6314e26f41 100644 GIT binary patch literal 174149 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 LU9L#R)c1b?NI*?J literal 29058 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

gVQ+e}lCEN997 zb$h_~3zQ`5A{KfA3AI@YlrMggODM|GM>$-@SDm2J_p7b+$>^3)biiz>F6zX^#&!`? zn^9>JIF$INw$yGt;+@Q8mBQCXQ%A}1e6s!DO-7(B)reW0`oH%+#;}C+4-dNJ@Lyg( z(lcsNgL-Qc4;`JuZM^({cH#|qK2^up-{IUqo8YaH@|Mlo_p2>*TJQXH>LlSBOQmB7 z;QmmCuSwa(>dK(1J75<6WOEhLA_w&0OV5Y9z8IyfD{(Vv>7nqapFk4-Jh4@rY#1|s z5!I`d%xw18JMLhekKXkJy=YfABTVi-`eIkID>_YjO; zI(^~YUR|11eoBu2B%jQQdO12T1y|EK0EgocFZy%~z% zx<0!(!GCt_Wd%Qi6r1x_J^4I<{i_W0NcDL>ht!2-qJEeSg_l>7{Nh`m(_A|ZTy5=XS_s@qirf?#-*+|bBKo&J?) zD3>Dk$B({JQIFtW!_J?xt?cRvb0PWjPt_Ci@K?OO_wrpH9rFEoH?&Cl0F2g!c>Pl- zI4N>c(lwEhmp3Df!|@u~JXC_lQ#9hEjaFipf%^5!{;Rj-N%K11Cy=m0tJjO7JX^~9 zK_X0WXY%Jk%iM7{!ka)_-iFFmR)3PefneSbr+K(ss+g`FPVdW6U!r!iE2}I2{L2*f zQz!GkOE8gPu6^?r*>9_d4_R7cXILKzZIznj2qGbzR;}{J)_>{7e$V;&qu(W{`t9%C zzVK4TT5SJS+N4KA>T9H(_+o0oRc9Ab=Vu|NHEIrlIPS0hJ{x!C`)W0eo5#A5?uG=< z^*`gc)|hfQvS(X%{s8QA8e57aBt(8|d3=_~Dw29)!KUY9vHO1R3s;-A$7N=UR-xs- z;*0UYeAlxB7w(jkqf%+U#A^6^v)szkG=|UpPS=2fu>Zj^d?TbsJWkMdfiJzZ<8G~XGrcyn z+Q|0&#)^#P5Pyi6XvpA*-ZCI*o<=ja*&VgU z6?L(i?v32|=YDuy$Z_pJ&Nkxrj;ZL}mMB?hU6@<)2<&~xv8a~V_+1q9!As5)hbS?k zmu63;#%;m-@KKf?_^o4dvJ5I>O&k_l7W=0cJS%BdW*cEUo@7;a!`3WI1Qu9beZ8}w zFDHnEeD1M`U(fr{*YQl3+dW;_h#vqvXOj5Qb2?e(3H{Xnv}ONwf$|Nt?x^Dg#E(2J z-OtZeJ;_$rfdu$q;Zr_3gx-Iw%&%9If$nRWjlNHDpWuWf^`5q4U(|(6wnCuc(G>mc zCSG5P2T*-Y!NGNreHM(qHVa1nz%|_d!Gz!UTRs10J|~%Wj3t0&qdv232Bu6Gs!5=A zyxs4%#uz!Yd_7nP^sC1~;n@GC{_Wa|%mx*;I~=i7@;X0X+%7wv`FM}^CL-fc)N$?N zqokmCfo!mmvL9Yqu8EbPp&Z!x`1azm;Ui`XqAzuMyt3Rs@+!CxcQzg6i*_vkvUA`1 zeb5~cP-`XyL$Kq9G%xqoNxkcGedfFxp09e#b%f&e*wjWs{w<6#^DoGhsEKZn|>EpbVv|Ev}J2S!XRbwFUd_|3#Q11{9CJ zy-Kj<6I_ge`?jxA@hyn0vP!xv*=ezYpSrIph6iDS9_zy)AhC$~Wr5iL?Zv(Id>wZ$ zv4Bbc_LP5#+j?rFyO#Zz6J=%N=MSf+OSM4gp?>SQ9%G+eagSSR)4g=du(Dif$ z2N|5eB}Bv2?gZK|hYQ|Rp!O6|Q%%#`t~NWGoA2bfuVE4q`JL-SrC3o??CkCeuD!|F zI&38QmMHGuIxriyr>8SoDeHC=n{~Ms+~_iL>CXO1y3`df)@xXV6?~B_t7P|;x}BPc zwOJ>dHfA|q^$+vwe;@P-5VfbCI+=b6j%RWdq$atj+crE-xHZUAsY#1eg8RfyESiJO zv%Kn`ins=QIUdm`Qi=28xl>0%pV#LH7o9S)v;G!y-lNlni&(JvuDM9H^>$Eu-cQ+$ zDYmQMPjvDWOoE0E-jQ!CgJ$2u5`?^&3Vp~U!b zY2n^Aa29bY|D^wU<=q&gk`te!LoD{T!S|;38$~Vq-`D8p90Y#d-t>I@O1`G+C&XVP z>2TJqe43`vWjp?uu07gtTURslnv&L*=!3N+V;a=NX(u;4!%f= z4mz2X9#nd>a!qHlst<)oovj24nNPa0_Ig#j473;fY%^+l9Cl<81Ts5*j~Db&yqr4c z#mZd0feypDvuc(oZBGXm$g$K$U-0H$)rzrPT%gA^X@z>Y8JZ=Y&~o*OBx214(o9{M zWcGb&lluXlt(E?mGxSnL&h%xeA?)r1v)iQsWu>BaQqlh*qu0+eEC%_~rl9bRaY9EN zUG!-Etm@H-(dTN_VtMOCbUW`~y36&qFYKvfugA+z{7onqn~aHu>Yl6149BCeNjge< zNq3FWQ@bT!r+v}C^50Dw5qGgaG=$2%WUV`@59f3wih7xMlgq&0GJ1Mn@|SbXA1t-S zDtlC9CKJM#_wjVxYr%U#P2J{S5&7V(? zTV~eZb02MglrlRJP{K5xT)er#hmXs9-1dJO7}IV4ygga5GUHf?FlD*wsrUN{S;*Jw zamB&=w&CQWCgJL~&b#L2cIjHFXiQI~%f>fFv=CPUX*ZM?bC$?~-}U3I^*o}|9ooFz z%c_-5<`(+IEne_+l&#%Y*^=|_N&@Yc@K6SaGD76+ci6fKDLzC+sw<}$4m8Y@Z zXGa*P?@i>?GF`-oIU=Q5FRGSuzj&5m4^z$Gk?Asr>ejzK9>ry_7suq*`fLsu#I3DT zP3xIg|B>TR(Wd+usxtEZ{&Lyj7yWC~{#gclk$DtIBTf97ya+Jdj9WfT{!l+XB0k^jl{lL7ZFk;% z;yje7m#ola;^kdbX`*SQe1Q|XS#g;xETmnZj}yFIXl;?;T?c4ty0jPnxAhH4Th^|zedvf zGHbA@2sG5g`D&%E%n}e7w9Z=xicLd9!@nE z>0iStfW)#9#@r|{uef9&Z)(9-Ty1mn%w(LrvWLUoBEQmKiKHyR#bQaeTHVg z8qR;Je>stqL$FZ3kj9|ycb%kLKI84-<>}?Am7AX@3ZbkL>C@?`{Z2sO;3yPdotxgf z=|arMc_gUX4KD)S!1V2p+HuiHAq4`S%9zqvO*%y!l<3b3R2Huf;DDQ6ng2H7wR;YRqKeBlZag+Y`djP%af^zvp@fV>_$u<1$^q zahIZA(!g`E4V2g?-l-b@Eti2TtiSX_FK!ci{ryy-PCAWn;$7JeJdVtzT)y$i0&hxj zTwZeCU=|W#*UyU5#UlN?h?Ke9hw;pQa!cl{U-mOLZC2*{-*o@7%%T5%T)J|QspYjr z?LRR0F4`f+JC=DGti_7*S+`Vqaf+mjVxNAczoi;mZgR-sHGBM*G1|%h?(LSo@5B7H zyD#(oR0&7J{-=}7F7D03hibGeRxcLcy5wDEvuZ_<*CLw`TpV9mN8zwcyuVyhA2@lm z^j*ix)XUHE;%=_&m%V3@v8#8^MoV_HK=5VSNB4-KY)G`rL^3rslg)XmgoJs_rINh7K+^R z-e{YD+MUKu6tNDR@*aek7B54PmMWk@5KE>q5a%PyCzDNxm1a)O!@zlqyk0|OESPW8 z?^=CZ&f4X1mO%(f47q!0pIkd$&REP?YS(6H*joC&QF0Yx9OIZB*h%nx>ATjLf1IM3sI^FT4j`U0ofJ8^B)mgPG{_ z`G)9gIy`TtCno{w7krAuzkmN;Uw@s{oFV*z2iL59ZZHIXs1=**z&$TV*xyfDS{j%N zJWrC6v{ZJKimZP$W8c;NjA#TTkWp zfT3e!_lvFm&)=?kR)(mNLDv=m38_rG*7Wn|bFjUP+C`yz2{vu$80vTWWLR8fg&@Sf z-huhJvig6S;}@n5KXZfq^gArC!eYv~kMG@P_tu=aHNqeGUv*6I!bM;-VZ*i4QG1C! z#8AW#!$%z9H#Hpp*-#>6=*4M~G84YHfRn-W2)yU3+fsbJF{kD9I&VJojxQlG&+;CtaU4(j#S7Ht6iHY zK*?w#&$rR~@#Z_;tC{kgz5Tfa zK~kmjv50u@ckIH5SN8rxz}=`{=w{t9&t(rYZW#YXyGIEX%2`%(T@!9bb_$^z)4? zu*zB4KjoPTez(_UxnL#&~#fgR7bWPeeyaWwYub5!N6?M`t`YpH09>!F)pXz zY2uIQo5$Lt(gSrSWc{Qyj5T>p*c|QR84GiB4?|>ra%k3nGsj#%0~+Tm)eNep`Dnf` zk`0fUj!K27h}pf%xzKz;qEO7l4Dxt*ShfkoH-l)&HWtP$C%)HRG#TZnGN=W?SPw0c37`iWWKYm6@H#^I$mEVQAg zR|iq!^Xpa5j+WO*GOe(D?A~7f&mC|&MhSi30W;k9$yP0=VvA+7V8`Kcf8J-yuwP3v z19tNCmK)jr=|6J&mx*Se=~yC04(42JZF<|*LN(|r zbkbXlIKO3qywa^SPU`xvJN!<$h9uR>--ATEZ-`j=_A}8ewoI~mANw@vHzaY2`J0)O z)W>2Ff|}UvKe{y+-Uq%E{+ZenbygG9kE z&feN;Ax8gdkWQtcfq%&f4VB z+)Hk9GRNbKCUCy+`(>3giU_`MtWKHvFAsI%g%`r6vHK77e&Y9#!COb2GIiPylwr#J zb5#J>}`0|C-tYDLhX%kRaUb^h(&`D03awcU>? z{cUwETL>%opZF7w^6d?Ple(M;p=A>;n*uEBP5}#Ffgx-6=?(H_(LwKQy=&b?d4C2B zb@Gi?@@?vkd@#M=J-RWrSZM!U<=pmp>$9=(#-T!2pHAJXrfy^SulOjsE?$b(N$`npZBPqM{&UG>Bh3E!@2hTe z?rt#7Fo#@aSs$rOGy67*2Kf>#v{JUT@3iubtxF0%*wQdB6Py%Jo%Q+#e>HvfprHw| z96z!u6sRzz)dyua*8ZmYVbj{+ltiadCQTTpo}`@$oArJ~xbOP&sk_n!E$GP+mgap6 z)gl`qJa-dy6g+VW)yDgbc!t}M)*el@~z%TQGd4uVptZ^onuK?2^^fnwiT zEccK9J_?NOK~p;g6usERbbH_-=Z!H0dJTaYNv@OEhIiuz>K@?-((*^%VpX-9{e5iN ztoq!&ZNQMc{@jc{W{G=q?nvq`y_xm1`~F*{or%?kPQN||n+@k*b?T!pN1Q-4Xx-Das_ygPxq+VvWETD1M#ZRxiZI< z;4{aihkx>9j?1Pj8M6qSaSH^IAo#3n&}lT1TD>jrTXBu*IJ%K_zA76_3sStf?IlWK zvSvw;{dw|T2Gjyn{x{E|M-rz+PJq4~i7)4bY4@?IktleRm%&7Ey8^nLv4*VDh8 zY;3CI&T43dl&e5B^9^FWqiu*}E(rMmg9x^lTKnZU55`LxYcBlo-mG!SFAuyfdn;{2 z4Ii{(D=vX`R>{l1ba)Y+pZ{%7*Z~Y$ziVF(HVu;MxGEkP5jNf6`KLnK!-iOFx;o>$N zcZ30q$+MO+{`lTtkfqCTK>Ly*r*z@C;F)-^CHrMTP4v4GO?EOaE-vy)bH$YETSko{ z`3!!1B}*%-Wt&rnxc1C;i28>oH$R-~hep^Pj;k*C)_74xM152M@!q))i%jOV+<-3_% zg{-V>=9^63zkmOtY*y&==Z+lO+)nuXdAKRl+ZGAE+>z7Ar81}z5)!foIb9#9S1Zxw zbKVgX3;WO9r_gH=-G}OBcXxc?V$Dk7(kz~-G~lt``W*oVtF5_&Q}<1{y3)OIzwA@` z=tn`AC;HsAW5=jgQno$BOMFQ(vEI1rNwpS`Nk|1_dA3WhrC(XJ{x%(X-uSJ=HOSg)`O?IVQ`=Bm>j0zJ_&ysB36 zpDpF~t|j_j-x_LmEq^qeud=QR_l+lSW{<7E7*JcZ(ApxI+2TFZ#XoCS>g<6Eo$96j znSbmz9iYgL!b&q6b=teSEw~I0I2*aJ?UFv=VN@{O!Ixz3mHo-R3wWBPebx{$M44Q+ z4}13?C=>ky240c(^M`$v3bezW;qoXghnmIDe&R5mHaN8P4+p{>62$)g|ItLi;`jnI zYrQ5%MnFvjhE7pLx8%h!rJo3zzxor`*;aU;+n_B_dmcV=140gj70kw8W5`S00^=GE zPg+LQnPMHt`01%I@@Cm}}K+(U#fzU$1{a*(kv|xW(YwfafycrmrTn(k8P;)ez%HF)T?l@e14ogBJ?7ECvNBz zq6x%BKZwBm`bd#;1Nj}63u8-4sZ>-tn}81acS}h72n9|M2I|F3vxzTce+jK1!bP4i z136r+8X1OTsBC7@y95_c4nzcT*xouZ41RjZS`|}_AJnYcI`6}Sv4X<2;dU?u>ESR{ z+%T}_dZa~R80On6E|3}UbxvMmuJ&K~eYO_XN!-t<#RN`Gu4JKgK3zu49NT5U0`YHXqIEVS-Nf(xu#j$*OgCC|MQ@z)u!;%i?m7CFDgw-Q^eA zI~O+n9S-7PloMC=0o{PKas~rQFAEv1RQz(Qf+rE3$bSJlbGYjpR}cuScCqz&6k7PT=IczL ztit-W37hbn^=B|i4Lb2Cev;8S(olQo3k+ll6}mTcd`=E8e6Ho1vA>WhE5z4oG;fM- zZ0AeX<-a4tluOFbq>#}+z&~8c zohA%f3P9SbgJFHraLs*aFWrI){f5}QraF_3LtOg#f{@55k-XpDwWS9cRk^LFCLiXi zkyVB2iVR4gKj?`gOA`?MEuASb>?mtEio_{{Yp~@g)hgC}p`y#_Y9mgZuyxG}PhR`o zeH)#WP0#j2ejAQ6!f<<+8H3h#uA)?(j+{<&%F|{v1cs~1h`-%FM45uxD>YTKLS2xe zyG>=4!1a)`RHGQvhOOa`m@L`$Xr&|hSwQ@dEUrYHU*Pcj2%RC^zrp%fdo<(bmW9Ej z3`s*o5hpDMp|S}Yw2)maFchWo)-1H&GH+mwaM!UY@DcTQ_vVSIO`JkKSEJ5 z<3_BWk$n#cyAxo&9Olof9E4KYM}HJHty z$XSE5Khj|sNzLU6SN@qhe4bsaUzhhETz2bRr>2LpZI^hSziX_zFEA3Y8;Sq2y(lu& z^4Fd#9N(<@a*`N71h?qL=Wy(`9~Z|xZhc7B%y(Etuo8y!H6bx3;ZML2tQUr%hCBSi#fcJ1hg4&KX)os=W|?X1~cxxr@w4wK*JGM^A2E? zV~K@3fk|39C`(Bk5+kZ#R8$mX27??tvSUecallr11}w!iCIbNE#O6&~!A1hvL_!nekL-0ty8Gcl1_cdWsaXy{2XQAYhOh%Cf}ihB4bBloJOSz)F6fgsGc&W6 z{pUC^5+3{%@namh87W@P-`{WX!%*8F>tD;nXa=JRM9z5lEqJj0D54lJqPf{*FSv`q zu-P35-I5#S$pXYNFa&+s=>l!b3nn8D{uKazK*0pO-e>IHNL>1SXT){ObHv)+a~8Wf zhK10dz|BMT+5_1}VVUNCXW%}$?t8ZiV$L^|(I$t=ML_4V-SaB{aPkZvJJO~uiWx6z zcScDVRw{OG+h?K4iTGd=5EQ#gW^GyI5|feuR_*9-DYWdgTAy%xwD`WO^_vjwcC#Bb zEAQxZiLDK1zd{_m7+((`xi?C|AiQ{b{O34l7O2$35{_u;#KdHh{C6Earu8!f4Uti) z_a?{rN+TRKSiBN)J4luK|AwSn;bnQg3bO`&r{$+lp!;e{iki?(TUTH2fTmeGm&o+H z^rA^3I-{9(z`luq$j03@jPgrj_L`!!6{T&8K3PGtK_XoIuZoJ~XcQMWynpo-DC8>P zJ&C47JN+4o2zHi~Sf%pc)jbDFQ)NC{CaIF%O*_lIa~3qp{#ZQxj9fq=GiW(ICwpCB z!9%&38qZlwY6_I~LE|cKx-4udTLLrBVHUx4}8-4lb|kgyJ(fa}>z8fh<{p>z>u{bi{{qGfg~Nq zXHxL?|7{mM;9t9ok#R9PIXO8vxS!?Jx!DS$kv|dRBTeC}eT$V3BZb^y|ao8&3x-<%1SBg(rpsn8R%1ZgP}W zR*vs64!C46{$*O*tl#1szX%+$S9IuX?ChI|BA=`7_(v1pDhxb|;QwdQ5-(UGdNzbd zN5e!xLBLm!WVMHN3S;LoUG8Us-o7)q;5Rq()Ii?9z;~Y@4M2BohXGiZHHUFizz39Q zFlL2Hz)@m&6aFgLRzO)`0K7@_Q+KV`XZ*k1spDPs{2Qmq*eRhClr=H1WJF2A_-qF2 zGZ@3hD*P3*ADcfpKbgG9=$PskuM$s=RPpm}>AQ7;vjH|_Nl3Ip$q+UQuD zfcPss>mQ={r;y*!yHvOlAH`~8@Wd#bOVjbDtLn(&Y1ixO#;MAoM@Dd|Wt3JrJRliW z?Ofw8@m+9N{yf43X7ha6`}YUiN-@XySQB$xMz!^QRYFsfSRoeW0JT`Ez>FKW5D}TH z(g&P6^)eh)yl)yxs{MHbsLc{Kdbg|UNmMoV9`O^IDduJ*7SF9}xd)_P?nCcaCr61z zzy=#n4L@$VqY*fUx?C!nPjj;~F@p$+%9q5Yk)-r{-|ns<_MCoKTG1`}GRnHcYJ4Bp zcTA1Sf2l_FWo{L|di`o2KS@Z14KG3IyeX?ON#&G2)vrp8Z$-~yJ44x^u+h7z`oVX- z2wJ$h%78Zg7e>*N9P43OR+CTDTlbzXeB&>l4k0rw`An8Jl@~a{wL?GavoKyYNzcaw zy-<=?odlz**D|-L)~CkmSH}Z}wwem*_Xk8-e;Mw6bQCk` z{(U@Y`cHNPvjEaoCuJsNZ3q+vwyPS;cV|oWPDhO=sN~PC>aFa0SkAW!ZRo-6Gj;m$ z8%)Wwf6p45s@6BU_ZW?s9%QNZ@p!*2NF(t#l$3sFmoAGBiYGhAf|IcBrV86C8sC+B zO1soB61b^e6tO7O={w>c!jOZaNELG8Ab-ulHp?Pt_-Klg(l7!XR2*pq1r|na-;of% zmh8&TQLtHoA*!HBa8DJf0OuGLp7y%X4)gY8QhRdIgjco%?nJy)FRB$ho z2nH-%PsRMj=O49GTc#8R7GVV|yCmPj`QcAr4brVk+nUKHHSSp^;Y+qPOI0m^WD73k z&{D4&YXGwIQP2w~dMtKA<#fXnoXK4;w;$d&`vKtth$YQ~5T{^C2cjZMJ>4APoi zNOR(Kd0>^#&G&g!fS?2}HDH(x(k~!A{oQ$h0OsyUie7_liVNCt4B-9W17QX@j*xD< zL97GF^HOTn4j!W-fm~*hLu#-1EBOE8zY{6m1$#aq&8>ZuFghBLa|ovG7N@uJ)A8sRHoI=B-W87 z3AQF7>DM)LT1Q3M3)_Q2Q)`?u!Jg*IY|w9WrsbK+8w)`RFrX;1C)xQZHj$N?P2f%5 zC>DxD!Lw1VCJHrS)PB5sBFBM*>2h_c)8@iSe#a3%XSqdnu&JveN{B}DiHAG{74f>& zf1j>zNmO9_7wa#vli7U7O39le4tr%ivLY<3Gc}G+vY*6s)V7`MzIM7Ea+fK0{whzF zmJ@ofhs+C0%D4bE;I%yh+m)Zshf46be7)Pekp(T%6qcgvH~Ko$p}j9<<bmvr$y@29cMp}beU8jiS2=nDw(e&!}qQ{4FZ1WC#k_OFvw_T<{AmF-kH zliQG+40Dow%8*9HNKmoF*c!BFS&%O}uJ}6VNLna5y_Z2mBZuLPcjxTVg|u@lEz|pw zD+RuFRa2a{oB_-jU0N&~ZB_FwYNO{*lW3-#f=|{b(i0-Kpj1ydQU< zc88=>MZTLv?--J;=?9dKwPiOS*0n&Tu&|6#FmM~#YI{)r7=!!`2wMtOeD^#nXS2&+ zX%<4%+SIdIOnMFIaH7}B`K^Kq-N)N*z&+d45*=Ksw`|HR!ELZCLIsdL-{j~)$jQ!B zn_jn};0Oog%ib3SQGeE$5BZLl5uZaNu^$5mx-3tCo!g_oh7^cl`@eTL^y}A+8l5A5X!4wb&bkj$vzWf*!m(?-O>bdXLq}|fZeV=Lv36o{ zm_@de8L8DT?5shwqU@RMy=%p8!a&9YWCgKB8m8pv&FAzS`gaN3hzFE%&n|LEf}fKX zy34**2JKq%*9Xh&>TA4f#LAq($okf=)04B`g#C1mp-QB+V2N48smt zeSQ7LdWAJX2Hv0E**nn`T#5Xc#YOK(({$nc3h|Cv7p776lW?6KKlHt%NK$du)MV?4 zyA)cP@xJ*e>aJ1s&if(hqwlr((RGis?ACS4XB{Ff8%LS=Dh(7WyxiLef$Gg)seG4pf zOa3(GyECZm=TAnmE)MgOsaYZ+$q=+kTU=`TGaFe)j!4-m8Ft(q4J?0)+#-BgADXRrofVcTxVyB%wE(7 z>S>|_7k>3hf_I;&OB23&Wq9zyuY$cpvGMe4DBe;(OeZGkj`2{t5|b(s0T9gKt!T-f z-g7p)PiJDx)i-y(eg_6K>GBs*EYJ6yU<2R#%nZ4Hx#q|!Z?u7j+b>HXPM4=*$s)}y zJk!kOa>&fTP!6xG%gCCbR@{MJ9u-y!6;{8)?5P9l=pO zQacZD6iNLc08{l~)YGHdR2cBemUJall?o=FC1+GBeo z8sy#f6Y}nv?bc-$ank;75|(fQ3E$aOJBeQ~mojKg5FpP==h7d~8;majELk z_`0z-4F^Lu6vg_o2FhP#!?dZ#WICXiI>9q3++NyK}S??_2wI6OXgbAJempox4L@tjXPIrZ|!6qCI>G%9|T`f*!v_IMC{O zU(pJ0nrQD$QdO5=OigwRXrY@wI3>~qb>O&VbQ|Lsm(QjVdy<#?!zVedCJIdasP+IgWR*P zr_(K{()HYM%sQ?C6^!zGh4_(mqV~hXeO|W}kGJwVm9I+NM?698Wa<^#315>yJ|j~f zOKoi}S5^!I!!gvD#5tk{Oj_Fz83sa`W+jp*Yli&8SXd4{n5B#t#iFYg6N!gPjE~>= zEHFKYDjIK!S~@fGKI^yTM06LURg2?S3CL&eI|;KwiGJy!e{6})X~BeKr-aQA$O1^+VCG2>$^s3@8ZtxNM)hnP|o>T{JZ{K@z$MZQ3YU0kQ%ZVxL;vY#I;s=0+7#V`P|bOOIP z_*q73V7K=>+1d)y-SETJJ}I-iqv6*bu5t;Xa2sTVVeom7v+=KSr3%$Pv0aZHLA9nmB}(`P7%guO#cO#4#H@!eZ)UY!WfO<$vrO@4#56C zvo8-puVUkV3=By&+hyRKnpfR1KW6&p^n7(xp;NEAgS$VLp;phpZOd`9R|-076q!U^ z(I4K&j0#6klaU$!$5+bWGyZdT#X*3&Ghtdgmd61+Mp0#D)d0s-pUrRqVio_)Xzm{l zZWy=%S0SlxmFC4$BQfaEmPgv}BXMrlSBp5+my;fI8~L+=ouJ;TWx(*qmpF%skON~l zB_obe4XE$4c0SJ+gUFIT@wvHGrE7#+*J;iljb9hKN{Lest_Cw*_WX z<0z*<;KUM-6=A_iBMS{3-C=$0r{ec zO@5o7yU`>P?+KTm5v;k$Cx`9fn#7Y~_1F^71M{c`M-&+Qq?>IlyH`Rhfir3uM~cN= z{kH<$_UPv$x&+KW1d&lva$n5Q>W$AJ!8V(EPkn*mo|+OhNVmQ9ZKX%^ghToifk*IQ zHrsJ~z{2yfMb5>mt_+)Z`y(AnBSmuH?gWA)oBb}EBSrn{M9c~m!QBFm=7ceeTr(Cq zWj_8FjWrT>t%*DF6=FyVKb8QK(S??So|Dm5c4~p&1k|hgbnPX|R^iw3>WS{+j67yvp4fqdy z6vfFB)ae?u8PL83QzprggXTLq3{+q|Ua#DM?KfRJrddsnEKHb-ofP&gxfHrtu&Mz} zug7?UV9R;Kk&7Qk2+UnnknAtLbldN7SiXQOd6kv0e)PVKN#PQJtQkG7hk;u>M^+RS z_*^I>Ol`Ta;ni(>{E7rE=|PD~i-OX}V~>_KK7e}e?pGSI2AesK5ae+=k})3hVeai> zP(zE!=s$weR+2LlmlqkNK3--- zUqGc?K%r7G4q939`!ELuIrI9^;3Xq$4a;$78C}!-2K~poBqgd;W+Ok)MyuIvP>PbK zC{rtr1w^KO1{B){KDx3!D|TW3Vy!&VJ|P4|#7(@}f)Y^b{AcsEx7xqYB-I(wpQG7%;;zY3z+HUw(!e`M+@XX@kh!*Y(<& z_S<1S-~|X?w10hF?tlv>Rp?cC+KH7QfoW^9`sheNO{(DAX1ZHuh6n~)nqFSiz{63+ zHKqULkimNbHUfKdL7w@#1LSB)vx?v7V#7)mV8o<_jse=lD)ulhGqcsaPw2B&2}W|9 zQo*^g9caJP#8!e2gM>LPsEI2{(=JfC;E%PqsthUqWM3y2f~vUjy8*{qOG`^t6s=`y zK{dmcIm=!^%LT))X94|fAa|L7)AoGHN&$?s0daNR*WJ9@@a)#B82w#SK2B7Bb;s=z zJh2bROM`E7dto*1h6okgJUXIJ5z01x|KH;?h*SNX2VMa99lC-p>-r_~@8pO%fwmj6 zEfn*Aw_&J25vrtzhX>rtt{SURIXR?07KYJjMUUBfD%!QzTTrkL1m+OKg12+V!x__v3%c2|!IfF-rt0A8$gLN6~9 zt5iKY(uf*@zZ5BR1Yz3GfL?e%x_nc{L|N@jN?#sK7+Mo)0l3Xhal`8cON1MctHzYh zO-)H@v9a+TT>Q4tc|041I@Coq$se!1B;MQ z1uogT&q{(pIB-78UHV9l&~_Y|0QtIf;&v~o#-OF=QoLc7^LmPHzQ46w8ornJFti3=O_%Mg{Mm@<4 zi)2BKeAt9PlJOiPAAaYKKQ}wSeE02mXSA;Iy@nsiL@T4w8A(=3w`RWe%@Pcp% zYqfoj<{16p!TuX@eSHn=WTWP6;FV((SYJ8g<&PlW%~{ej2302R>VA|U`sjgR3hB-f zbwk$1TccvS@W^^llcKD-u!afK$Ca)SaN-;3lJ=wdO?-PJ?MJ)KMB<$(%8E*$ustHu z9Eu+Kc*o*+1yrNwgZN59&2yK~jw&98y#si742@jEu_Gz;Z=hAQV`L5^8p&U9(nL}@ z4s#C7E-%2ReDQ?dJ@>y4%ft1*2JJfo664399cyzp#CKz!i(>)lb%Evx>LfByP8e2p z^}I2+P^6XPrN7WZnq!ioV8%8GQ(4}P{kDX={J-zKKI2IZ3%qSSA4C!aRsC+-r-c2` z6g*7K8XU~bLecv^aDc!A2Vlsv%~1LOuBrImK;UJu$EaJtB=~QQf!}y61aR{wn0{z) zMtIl9puwuR)Z+I5H{{)IZyRfKYbcmpdna-(ZkUVdSZ5yH?Tz*BMp(5TCBt^I! z`D823dnh3>YM)xig~4e?*VAj&@LQBhkzQbjXu}<22iI8oIvF?1&EadEjnD1{qKyEnfvdLc^WIM?!!}`AGpm=N6ygL`omWyW_z2Pl)G( zNIfo*spkSCkfJN3eR={C;)|jlN}wfQhEtjGi;;u-j&m_pJu*!}dXEKPRUeh;6nl#V zpGHcu4h9L;jN%UKVy{c*V<86V37L-+g0yFYoJaOxEEb(Zm@s%rH%~^A@O#s76n$y zXAjt{Ojx8i`d4g;rqa#UzW!YY?0TCht*79z!0bgav<vpCj&R13|{EfA&HoVMit z;__+Qlu7WRm^}qCpwcRP7^+I0QytHqI3jRU)^FtR1<0;k?R0zJyj@DN0Jp@GXG6xs z(86Rs9xfkAeU3uGIo5@o+p79>&q+a&+6<0%_atGy?FtU_s2!)#ce@e!5_t+6rR(ou) zwgLdxR^fVg&KOv(o}}GLPpX#FZg0h7Xx@sErgi`os;L?}X&Vu&eujkJ0)PJz#uFBz zK2RI2d-H1ej?oZc#jwIKyGgax%Kz z-ox8%C`|t?UX)y<|2LI5?R25t6hLK+xsnzjDmB|o`;n%TR|{mYf9TX0i-KygdmnIK ziQBAgfqO~jGlXfpY$3SPws`;KXqFL(6U4vB!{^I61)voN0O|y*E0sY93J#dr>=#6q z7<=kqh4Zrk?*|;EGNU!#W<}*K?;1hqn>eC}Wa75?+S(e07z=_72L7puOrl5zIqC*v z^jlCd`#et|xVBw<&6aC@qL~7du=$`3oe$q6XB4j%fCU8Rd7aBGKpC!qy@GD^ zESxf}73sn1csjTwt+tk-g*2|hM%`%TqX_;D&GX;1sO+6{I;$BR#aPGg`;}7}iAXf! zT{!>a&>3)u$9`OXdi3)2EOo&pcG9TA+W2X!-!UxnC1wvyWtmUIrA@*IqB6-O$bV5e$;_1jBQN@?acmoTH#E!0vkYedD%YQfI0(Z3>j8QpikxJ7mU_ecVpX!CR2Xr|x-Rb2W)@zO6**Vedux(P$a z-3mW^G{2c2FZq~%uAa~iD{nN4>xCp`&bw1d)!;bFUIt2>+;fSaMA3~>7Ar0eS)Kp3 z$>!BKLt=t^P^m~ z$@6Eex0wz7aJ_i=<9YG)4_4>i&Gs+7#qtxI)x*Yq_Qcp%0rKwbar$w%3hfdoXh}DJ zUx#q2s*VNwo1AGcZAA3p-l|OQ%{8$5*xSw4ZeOtYWnu8HW6B8nQ(4_oeaZ7P?@9Y~ z=To9VT;sDtR4NJt+4gKFKcPpLS31XnRYQo8(CwENVF?#_q+?ebG#B0lr)P5+eOgn^ z1kGFP-5GB%aNWP9 z8mulz*C+T`kvl&(L7I$!LQ26g4~^JZRPkLPl{s92Kd%Vvt_Qk=8+b#c$Nc14MGGo4 zt2SQz7U`xg`~}Ix!}|=F0(k=qi88n(5z$fnZ$pK~-=HhgX-a)6ip{?Dc*KlH{7W6= z!{Wz+FzItXl&@)d6}c?7qo zA>Qe1wWbmqaOgBPY{@|Fr$ud2A>C9(eISojIs-KgyLAF>Z8qzToqn91SA(7zr7Sb7 zmi%z5W9-&HILZ>;R|_B_=+2s&UnTPHw$lu{Q3^eP%rsM~Hbi!Mkv~$P-|PS>=e2oy zb_QD6-Zl@>j%N$Ll#e%<47^Q>Oot}t-8Q|ki-zN<% z^Y@UQu3u{Ykb2JTQevW%*YZ;U^e^p- zSB_0+RRgCw75WT9Tpsr z_R4R~W_lz4`D!^1wQC}b)*GX$SNJ1WY;ty_NH$(EE`0OB?SoXy_4fXCB`=-VZF!3d zke~Q1t}i!8I&iewaz+0^Y~<0r{ju*{!gpz%>a983y< zmiGW_4rs`ZpkdMwHzdRXItAddf`=d*Nt$DzhK~NowJk81YGn&$jvKUDEzAj4Edk5} zflOK~0>YaH#LtVV-%^|gBqD_~Y=t}gMy-;3gi z61ZW%kz3*E%5phsBF*pe`{!M5dPSR^0D5+*8 zl}LaH5j>QGmaWU+(ovU=W|EF;Bf&Dk#$(;AWjz1!yM0rR&(&PyC6I9~`*}MsDR0$R zlwNMfWDuwJ9%2Yqi(*YqmJO0j)jV+AmYj_@jQ z5~$Q}I0e@TzhwI=3S;K;Q|?ecisz9*q_aPdN}HF1W_9m{)hfnaBR-+;=YhBJE7obD zrA~J|@#s75%Tm%GNw`im{uYkm$$Z$U(2kuK96x|{Bqw?PplnL2IFQ>nf~#JCI#Qs> z^G$&)kS<)4;&AmY#?eGVrk8v^G#zmYx=DVnY4vz&dkmZLdragrj2oFzW&O_w4mP$l zZ!iPuCP-0$K{O0rvOw8Wflz4LC528+8w9|yv8j@VV4xsvKzfMRuscA&_rCf0Gm};G z7aZWNOCHE(AuL#_U+3A=KSSUK5SGp-P3d&Kw2vD_K@2T9Ijn=_6t8jv5lf@i zcJ=egZiNN~Jni$aDuu!}GaMFcpWOQJK%IA4*Dkdfd4jJpL zE$%1!zcHV6xus-eT3z;DB_$pK!=uH9$B(E^Y!~+r4@u}WQWW4B@hXwy_`N`KZ!#dG zMM~raB3wkW^78{U4`YJnR{8Gcba|W#hzyx!ep@_8NxE--J0e$nCpIHnf zVt|YG{^kU=t$M7Xfjj#WfI5)rtQ?|K=`~&pb=t9MOWVuc%sp^AZdSX&H?Uhg)^<#P zLm(%`U8-K$>~YK%f4*VFTtlE&*fT+&fBfeYf6$g)60Ms_*oMg;iLiLtLg^H_~TAE-id}X zzi=^i-xGf1XZnb!kHM$mZvq2@T*_3FAwx7e(3D-jE%FG&qH&0(Z!dV2vkwx4&RF6t zbCK%NJ0S=~=o{lm5&dd)Ep_)V!+38f6ww)>;xcBQnbv5T)Q=$%OSoKTv8L~vCn-5+ zwJ2PSXhuV`2v1%g9M1tSpxHT?X>HO>^&ZTIv^??8Glqho6BF1dq$m#GpQkjusALN>P20jb<~ z)froTGqPdzrV9uBi+E=0L@BE^mRPx6?%cxSo*gqyd7{STF}3;1;<4x<{eRY)>_7kh z+vCKzM#gBeRW-Aerd*e_x5Lk4Hb|30?)Zm#g(xyLKYAH+Elyl$NjR4Wg;RD|)3mUW zUt2aw86~Z92dk*iB|{y@~`qGXAME{)%_-1U8*K)bFeI6)>~U$m9|tibc{atC41o;O~a z-;#rxrt5D$wME8}28>=*Wj3-K=&lPW$dP=vv3;d!{`-k8w}l7Haw9LMbJ(}%8e)6>5}qLYl3;^Q7<*PeLec=_+s zsT_I8;?Y?8^1i|Pv@7ifT8L}GS{wl(4LXfVKJOh0<`lF;Z!`nuz2%9bWJV0Q9kw$iGzKU#gahEgnpL=&6`w-?oLfN3 z_8!L3-oE~W3|iPuSba+q7B8U_T68=dnO82EsLh~(wq?2f-uH^MSS$bgq6L#&K}bA& ze&3PjuH~NLJsY&CKbKPY86TD0@T7#NzG-Ag$Uur1lkU}<pL~V#WSEexNq$v*FZA@Ut^po9eME^nFSeR&%&qooAEO%y+c})1 znjo0Ny-QldDWCbPq96E0r%;EZ*bgwWx2kkDvwKC4u_ss<_-Y+O^{=G%T2z2%o986Z z5Q5Mav?X_`Y|&~T00QSVD!3JfV-+PV%+0ByT3+I9$SYUYp1h(TJGv*nJ0|^94BI*z z!#}21TQwqiZc94SGn>p6_uhFM%v?Xu39%e)?%(IT*SFkK{uaw;y^H7tP8Bw`?#lUA z_pEPs-OZp4off%#Nx7-nj#atbvFqDK;Bs~^ftG6{W?zMuSN1j>Ynf$bII&jFK5?I= z2#_h!@$3{dNXR%7)JlVO*2BxS<ch&YPjgV^Zp-1XN2`nty916iiu3!~b}2$+ zclPwgiZy^qIU^Z^?MWPd8SZ>Ei*aG80qE2(wdOc*KVLsQ#4~9^9 zVu}k5ke*>5hz~e+QE&Y#4>)?-J_DU?yGL|q^xMrYNRa^(gT8%-=X!Tlbmz|O(8*-W zO54d>mhTK;P{u%jd3}JDHQ(SyMrU}h?5u!gkM(5v|+_jo_M%L=_3yjqV(JWsD4xm9m}Mwl$igPGdc6G>icF zCEK-p4#4eY4Z6n{iWXF5BkbdHf#@>LpV%uS^`*^3dE#_sAO7XV`8_+jg2Txe{xV4k z((Ef7YkTQ!;B)tW3MjBTx%w$}i{#>w$G)X)B>w;?XcNNH-D*m(LM^amN)PhSFc8&&f;FC(A9`=K9H&&NKW2B9Ltw*pof`+ z&FF*$^B%^z9hn`=IG(^Lso@8VbUadfGNnvpD0yJBUWUN85AJ`diloZvzBL{z=|oGP>sg@7Y9f4iX5VOkG(oVmKb-4GXjyGG@ZegT4o+vHG zdlzh$l|z}9d#;I|W^(ElFeGkH)y!LpjJlh#aFpwvb9sqw9c5l>F0iW}Iz(0Z?Mqfx zCVou5X?i)?Kh0V3b+OTRRIgCcIXYmq|5ZHEk4%8iPgE{2#k4<>e`L_`@37_K<>4>s zzb@^Ln15Jrc_Wk)R+>NEF&E0EUrx@CNh)w2pHH08U5l#|tnhJSb0itzLb3Ntzt(p@jl(=SXb7D03|{}wnj zjVUUcQRpPwR~j3HJ~u6pSTeg7%Ut>S|FX^*)`4%T;P+r!;@zCyuuuD>b1`*MGXosI z(=tWO)XOp-;NeSn+9w&RPFO56uk$d{CB3Ng7fk+MW@)svXCPKvJl~C6(@vgrJxsP2 zSo@z$R>Z`q52g`Y!$CT~4XNj-(yzlty2}>44tVigJ6t*IqMv!+^T#QCD907y311v8 zL!f=duuecH9q&h}@x8sh0q9oVKThVVNcNzCF#F;5(o2Cyxz)vTbvqyElbrUeA@&z0zT{xbAtois zz#_}x=^UjA7vl1%4)~#qB|JFTq4VqT9Frn}h_8!VTuc$ZJ+&-g#SCZp*!N+A%ldLYyPM5K3+uY{#t_odqv`85_71le9id&@Y zn9Tr@oOK&xi?Uh~+7x<Z|yn%8?Q3u3)fB%nhu10PM}hHh25msf$vP_z6Dp zKK!I1HedxGP`}E;0LDjhe{5*e1=D9C=|D{8t+!6YW%0%%BVLHhUH<-K-%&`d=j>dD zqGoiQ1|wB0zEYRay@ACJ*UL^eu4iS*6C%Gr`SaPgNFn{|C@rb$J0}xGv)m8Cu4K{@ z#VA~)UaB9$P`+jyDo&~5rsh~8sbg~T?2CtP+Ur#p=QCCU-&+iaH?h_xzAMp7RlKE5 zVMMPrj~1Xz3327xf(qT1d|<8P zG;VSOR)h^E@xW{jS<*`9s*{9*F$6t(&d*BO|r zk&;3S=d+(r8tr2^X>uC+4&Nj7_@sh zMkE3B1K*VwG1!R$wgg4e_ftBs5Z1t&z_#4<=ST z#d)~r75EhalN$u&ooT}lasWR7GEHE06#8w7dw^C~JORgH6~rjj%hW3X%TG!DxNJcU zU4gGlG7!3(BtvjgbheaC{Jb=^J;ju=*8VFgG%Mx0f> zqM9{aX~!nrAk_J*rw9BS@zK$p{{1G?e<+t$*pVomVn7jLJANc^XVFVJLaQciA4W-! zZ$SFmys`qQ3g7P1yD`We3*} z^|B^Uwa(=ZvmvPzHXF4ys+!Nm<>u_S+g0etrcyNWBW^KTHPY)DWZ#U%GMdS%ai94w zbs9MAL;qMyhkZ<3_=;hNSF5tOSj9P>$|F{r3?!qkF5a!q;}rv&glB(?m)fJ{2@TWm z2^vcohXz?zI@2{W===?2$W*G$6DaA~yY8}58r}~ zf%cY7mc>VrFLdIKSD3CV`>Ueae^VQDXRikpYwlFK4x0TQ~ zw4QAb6glo=F`j2NU+gK>;EWLKM0YNt@$;mfQW3&dd8TBfHsfa!PjW#OSEY z74=ZyvLcFKXBHRHqiM9$4mlUs*K|GB^aG-gzWv3(%rm~`GR)q#hv=OI=yhrsd7#?j zr9lh>99>0x zD*x+SeL`a3!`PRtDCNEzThVwN2){R3oQgNCBOF`xd9VBBk1y`+APqDvNHjt+64Op4 zOa5|byL3nY2l1kcRfqj2iSOm)*-V}NN^6SLL!d;i8qj6OHGVE&q3iU6lc)Dq=O}yH zxfwiX+Ijs?*UrcDk?B37^=BI~ok+37q1#8$)r`_Z!}S-|ggZrs-QEecKzZl`divP) z$|m6d(UK_cH3Irm^jtG{>X6M))y!KkH8FECoc+|DeTbzNwaJe4qo<(gm5~NdnMQ_h z)-{^OW+{4>T%t3d!J2sK#}-)l8aS#t7wl$m`+&x*T%8vV`&B-z{IHAXS{t6N10FSj zIj*Pu`tZ6W?rhe-l?(=*d7X3cOEoOJ#ZNo<|&(D->Y%XK9R}R=Wivr(LTB%%#$5YTAd3p`{ zyo|vdR*64C?@I!DweGd$#;ibz>JL`L8N?|dZ;y52_yJ=yu7q56DI$;qirg z5=O42swL!2PA|;y>K1Us_Nn}C`+vZ2Y`egrkOIG6^KUd?4~WKjLQ{b7#L4R#s4KmY z0V@*~S7Uwz<#q_ww2REiuI&PsG#|bF{6nV0)9P0UBEYD9@QOQmME|l1BZ>QU%(?#t zP7s)<*MX##p-OjUHvQ@b;h^I9ulra^H6T?1{%J?hZs+t4%3R2^z@do^n9-uxk2zTr z=?yu&Jv&0|MhD9f|8setwL(vChJG9Su{U3dg*%bUw-Y~Njmz4j40i3BU6u#YHU$2A z&CDs7if45Z9fq1=WlM85N;Trq*anO{a!MuZMv`W*eRv#O%)z+z=v+|k#8-w#!_DkRJ1KBYq#hsM3V@_0VA@yb^L)#E52DNs0tbE&8YEW zeg@n0qRe2@i0t-&8;I{?aqy$&^NAXo`sQ_CcUBGZSv!grG8CNaPBH4VWF5=$0~U~% zlHqV%K?088b2{j%0USdP4F-Z*r)^Y!B7WHKk7MRHteqvzG5;N;

IC@M?8KU-G*{0fk4}9$1q6Zi@8+y~hJ-?tzpg>dg-YhFjxk4>=6JwRZnRyQ z)2CqFHX%C5#>%UjDBAzJ7RzqQU*oypR}>U5UK`=Ej9sx>fS&`L#of4QBAz3AP4nJ9 zdtUm(o9N4@<3S{0!65IXO})`p_$Kt@RJhwvBKN-g4_(K*gBUWKiF%d4B%- z>;LvSpLV(mkuW&1T!g7*el+Q~6&0T2B6VdU8W{@`R_Bo}a#;>c3t3rn2XMc1LBf=* zEMjK&O1sSrqMLUeU#w$1d8G7)qlW`3gu2Ik>6)B1$Yi@;in4;)w6wVFhmR3~`rK5^ z(tiC3H!MA&mwa>c(?|1pfxn6QnMa}HPC_zpr(VDXCM7G44k7Z+X3)uHqO{=~glia7 zr4w~NVf)B8j_R!=)hzl005yaHmvJMoSp(PXCrE~)3LXFX25W-Be!aH)tAha&8YnjF z*~4!|l2l0m>=?1-J~yrhgl$kj$Kqd(b~<^iJCU}<`d`}|Es`b!c5yooVeIaob!4GY z*q_5uy2d7ig2k$@bsX!)1(o-`zli zilqZ$b=pA~kmrr?#+O2-7=x9z(05E^ilBf?RB(!ww4|gHwo|guqm2%qpXn}K18=Nez$1;?zRO24LNai;Q`>x!rMcR1cfc%%C>4-u0lL&^CX3 zXS1Ts?HT+@+W~`e{fp^J<&h4@@uE1nBk!a-SAs&(!D7RK;8J5>Z*M#)lde(I(Xx0V z%&_ECwJ(kbjY{EYtE)BA*KysyFjR71g@9-eW54GA!XE%Vkr9WKud!s6m$!g%(+ZXpE0dL=VPH>8`3Jz(YvGf8W{hk zi$JGGBxj=VCL|N(;V*Wl6ljXq)C>ryIN5uKc`PSIVKR3cx-E=O3^_@FO(Y+%|M#JW zp<_BgKx?i~xXn|5#ZaV+WPoT%`g~LW<60Pp4xE@lm(+UMIK9Tky9h+AzD_^5PL%9k3GMXYBTK%QCrwXRe) zV>WzRXOyt+Q2AOB&F_0ZRUrFAPDX}&&+!KOErNBECP3?4L}5Us(iPbK$2UZHlN(S> zj0tOCcPySje-eXNN1Qu<#To*>P-HQ+cwO&(=MOtQsL}-Id6PSS?1;co3rpFh0@2-7 z#cW_qG4^UK3V(gWYJFPNE#^(+I<07YkA#h$|(W`Hv@?p7YLy#9R zkbKqHy#A5|I^aSIP)%uCqt zsU?K?y0JI=ugH_P7Jj0^Tk0kvt)Fa+|B#2t$Vv@2E((zx?hC z$G<{_y;v?DIBfc(8yMmJ9M*RkNxjvX7%TjOQu~ut%_mm>%Hwn9(q89PHV|>5W>3F4 ziS)SxrgtD+WKo> z>@4VTB(GmVUZIKm;V3mDg%KFfjF}DaZxyP>GuwhMkC#0GBn`M$x>+r)Dchc(pOtBw zK4bi2)|1{_D!b9iY)kaud*9;w;O11@0J?l1pP`+4wQxy4f`W4JM$hJAWn~2rhq=1B zc#xyTGVlD%73v+`R7P{Y3oeN z+}mEz_D*@e7eBHso66uQFA;=EbSo^+cA<1$v@Vc&diGvwatF-9>Sb7?$PPURb^vRb zkA<%vX2|FBe76haBnkmY2VfE=-~(>C0%I=`w0TILmin}K^<`vG z*iUy9sF`2pDD$#!q`9@bvx|@Kw_f=LC*TfczXbq6GD$=zmHw(FPWC-+irg8U<2s(T zmW%>L%+A@44*i{Yn&Y_GV;jXBQ5=4&IR)3dLsI}9fLd~0ARQ#pB@_R=lnbhuLqYF-$7xu<1LPElL z^l8@@XBRTE?P(fFp}98k=Jsz{rGNcz8_3+Re_ox1qqohy^910D`s;(1^gToKU)Z3x zM7+ffRy7ovMi`X^dm`4I;;bSqV%+&U7zR9%x9-g0Lic_aB zKpY~K+5o)7wA0eu>Dnhm}io{JUi!oW%crhWCkQcqVq{Ar^M#GZFd62Nt}y zp|w60i~cq*nZu@HM;3y1CEDJ$bHuPdvEJ|Lt45K7bWGT1@K0fhlOI`p43 z1#jan=gS4i<~11p%>8I0ak1+(zK?7KWJT37bw-3{>wyVKNXTkgiEC^asculk={Mi* zIX8Zl$=mdD+;&0=7EiAs!i$~lt-~M7jkW|N(Md$SZhy+P5=QgK%Fd}ITUw&XHHg31 z>a*If_Ff4-I)L|(k5gt!iYrhh*yDC0dXu2%=65`ucb`s4wI|9vfoV0>Y|ei5_M(!= zspYXBWgG!Mdt7z_n5JTM8p~Tf&T{FWvbG`c4b~*XO5RtIj^;&|fh2q)jWQ~iUYh=`jI^)jcXa z&FEi^EU8pU@Cj0T?t<3vue^snEpoLTWenQ&pC^`$PD8OD-~znIGtAbhrr9m(_JIgx z-|rp~8!Pa0c4Wi34<|bqN*Ij30?HgQpPws`8eYnEe87~ZN+w9KAS+y=?Ct59(oW4L zukHpWDcUyb?xr2=l!tb$34*YVFcWs~4hgXB7l_nH2rCddt;VVUzDboi{-b^HSf%?Z zP5u)Hp8Fczk!}+#OS&FUpKnz*Cxv^A_8dNs%fHK?CHWK(5HFX~V#2C70y2tCRGEU z-VS|ZJIXnTy${qfgPF^r5w6u_M%%o;)FO)ji-J29u;b$+GqfRGY&ee5&zn8I)d91t za-Wm1!&e2IdY?PgwnnCvRy*N^>Zlyy4NJ?}Xq*$a;xz=Ts1x^6qc+#uT4AvBNs~u9 zzd}5n^#!fTz|^VR;pOV#;UOi3@O8|AhLjY-#{9!qkkefQGfPNl=-vIjfSVzm@^ZqE zDKIt5D=35w|7>+Flaggluwc=YelQHRKrgaB+Qxw%S^MqgKWpB*8FkW|8L=GNkTGY6B zmt^jrR}B1aV+nEt(>7@B{r}hOeur{=|&*Lr918|EBXqEoQd5 z9ktqA(tteZCtY*EnaXi*DhELAP7W(TEeMpUyqM_UJ`paC$3EHFzaSn#!4X43ogb?7 zs|Y>$a@KyL1~UYIlg>?&n4fPoYE()iQog0fuv z#|(W6l=ps5VCNSUG~BelIca)>r&h0hR9nEG3T%rL;wxasiotXQ2fZZW=q!^^S+||u zc?S~C5z~sJnY^5-jQmEwLU?$VcE-|QxSWt;7h&S0SDg#;-KMDCIEE{`8$E0?T0oEa z*3~o2p|qW5)T%HwTrD^X$~qZ<`YnRj_oX-okp(1`;!ei z2OtYFc*Ym)cy1}I`#{FuK;Q)hX4YrF$6?kd+%yG89wiJALl^M^7A*l^U=W-Kd(a5T zQJZIo^9&;`GKA&fyOl9m&3ZiHC>+o(ASEr0$LCSqXZ#HWqkvC)NN%z6Dnqyn^wWYN zz8hQ48a+xVsQtgNEUEZYnRkX*)RaZVf4)cK*yG0_n{cc)y9np07f?|D%t^CmZ6j6m zl14{Kffb?38Fl;z8}WS%E>H$}a73o(0wbpzD%Y@_)Rd=#kXR`y;j}711R}+&oRUO*t>7qo$(oy>>S1IKWc zAhJGuCsyy(?g^~gYF5u6it#f)%VbU)=)H&Pn4*?~3Ym|~#XGsbMRiJ)2L=W%CRl<< zt8kGiK^qUKmNuV zhx{9=03eE#m8bq=W`Ni#cnq>N4{&5!m%KODw0sv4H4gPri4JY*Eh3!rJG>d>Y%X;`kcoS4Mx7P$1&$>eUiWHQ&; z#ibr+BnDCLe`(*p0fmil?aoY2PA=#@11Jq2zg=D5)sA}gTgN5I6m;4wg0u4+Q54Y! z!|t|~rTF526C8QBP^X)ghu@Z~@aGJqC@%AhUohPVeanXK9ASFYDOeovIZztiwe0+CSWr-fWu0YpgE$!XJkHsJa@in zfVs2qtMsdH2DEsz{B=X>BR(<=zhYcg1g)|Ky=KHb%~TgjnBawJb2t|pDg;b;gl}9h zUo6mS-`0u5!y22ak@k-D^?lf)Ig|c%{;?VSfJ52;q0IhAXr+x@7wdgJ!48ovd>UrX zy+VC65K_$Ip$186%}_VqusP#Gh2XzVX@r9Ct;R~vtcCBljrqTD`h(^J&lweA^LZ+N zvKO3B1d$TAAbdRh9g{3sEc!DpABq{Bt0RSPw49K!=iM&IV!f#l4P& zP#|6d4z=z3ViFQ|AOt)Con<%nYZqhz{#-~+fEW?kNW7!p8W1A6dQZCeww&$~DP?aD zYAoU`l;89?(<#Gv`o-%fS;;73oA^`V!lDEIsWA4PBpQYP>jwo=RswK2Fuisff$^!` za_aCRiv;X0a1mMRLGc^h$Bgk3641t22tq&uWGR3(tSb|)6&FE6hn}4OOco(z3L|YO zLdSm6X0M<9bz79z)dz!Of2?z4_Rs~D9s^fs@RSgc>Tq$>k@89RM(Gu9Qq|CdDQSHV z*Eev!G(IS+)fLKrj-q7E#dsp>5N1Z#p`k?>YVdCRNXzM@k7%~t>j_lNcW!B@) znLK`tO*dX?oi^f?6Vv>JYqts2cY?;LtPQ>C=jMd9e1ZmR3o)s(k1xN}R&{f*dT#R$L6}b#nRYmy zH>$ZH88)O@78i_$z){Z7b;0o4s^JYbLjYq}9H~_2|9m4pE<7t>>$n7P`G6|Ps8uV* zloo6b_@wzYAL;0j9s`_K+daXlfs(NYe2$en#`A#8j7GVRCmoC7>Fu3_U}9`6^?&3? z$@uu%*Y`%*6##`~NDNVkV%-*;7159`0r2!6D7XIi^JofZ3-bpI>iU>#Dq#kXYbkiw zuV)_yy{Dkq0)}l<9NZ6v5NV{;EvZ>@RPO~K=REbYoYi)c&V9Jt4smS&7|t;LAks;m zj+TBCk`R~$pbESb#=m}qcG_4yN4C2;2xb;drs?KUI#RCN9q@`a{}#^z1IT zaW+N!o!!3L{e~IyJvrlqnBp_33M&;_?iq!+_I2>Y+@%#dw#>@5n!<5mal%{NG^>$0 zeveD}6Iv%s4)1qFy-@7akMOEtn^})5T<-nli84W(rQ(?%lUN2Pg&2rfZ`)R7Z+_x9 z8@t=%F0~pJMXt>e+~VP39qy*_8+Ka%}q8EJ`UPnE_A3FS*Gy$!wCYpg_^^?*V@J$g zZ~vp|OEQ*L8DF|uFpVYuAmByC67$~!xYNCxjezkKDt%KHpjrk=*iyHA)@%Bc-PSo) zuSsg)Lmp4Acsc(uox^VapXps(x{O+Bp_8U?c{y^(Em_5Hbs4D53^jGBdweNP-iZIS z4TKtjqH_wo5ReBxg-4PH>0{uidBt(sm8jl>1^vbDe6!sdizVWP^D|}n)@H#4L!3eU z1{2Y+M)MUrvJ~pA2|Wu&VV95mPx=zD&aM7; z{8FEg+vydl>_n|rstRD^y#DK-#xuB~3kFX3w7p<%E?T2_GzzA@nP{o+316d{a9jV4 zYKq4DY*K7V?VJ2Jo_Q+$pLnKmFR;;h@!o+!e!bC?hxa$IjV!*ZX8oi<0U1`9?c2F~Q(v%4UA~Kk;|53H5(LpCi&UYP5@Q7i;*v0y8j*CRRqM zznrX;i%&$BUF&D?9IqJKhuuF5b0e2 zSWkit+Iiq2_20pWxH(?JbA}u_Y|Nb_f_AT?LQ#12leK5pmZ^7UBF&-8+!?lF#$m8z zDRaL`TV^~2duUi#>E4axQ&QoUyAd!VpWh%}Ug+2R-R%4N6D?f2PEX;VIBf%=)|SJJ zsyC^o;fo#__I^+gTRm5L+*+h}1`;o{F(x92B#pDhpVgT1F_vT{UN%2~U0}k;_e@LV zs7Pb<+}EZ!G@B7A0l)cm zJqhg`g^pSo1D?J4u|v$+Y{Cxeb6a+<``@udq-}FdZ`97Bbz0MiC%0TX_(lvgDn(MI zHorM3dr@$>`)P6&7z~(?t>U)doX!yO&Z?DRALAAM@HuKTCYh3cnBYHfe`>~*GZ1)$ z9&A}(h1gJ2@cECCYV8r9;EyR9eXYyUB-zz478N3IIW}j;1ioWdhsUI8-Q9g^AZKKi zLao(yn)U1njO9XoV={+UL8nO(k>>Ru(`1R6?)-??OG`#(2d~)vP0EEq`WQtTJiZi zDwse~D+8=J(8@NFLZ7DL&8w^n>^DTq5bnpzfg}9?ZA7I)Gc2ZPaf` zInG+dUMYHV8s&PaGxFP)3&V=+je<*g?0p=#w<3Rm>UCOV zN*r*NJ9GEhrISXHj3?jW)ZOUafAB_tB=l~O>YE+$SaO)e6|OWJRBBRtv)533DuZ{2 zByZkmY2FksM}^4c4HI=SO;yvqn~RiOe$XI`F`82{DO=%lr>|%*F-fZR-P=2j^wA!l z2W2ZamX&tNB4j*DRf^dA{I>U#i7saXBn$>pX@kWIJ!6S1*xyQQ<7TJwReK7`Jo`#$ zi8g=J08u(QGd1~Z!u!x~+2uXt+5&F zU5*#kbI#qWjiUo+inN9~?6T-m;YZ3(+$^OxF+XMu_E_crUd^XJJgY;b9;a0BZR%_~ z>w2-nPtvNKc}uJB0#+e2VVR_iW#Nq3A9otnXc<-LHD!tJT3Qhs%Rnpy?6{mv zU%W~v1=1&b#S#XxcfOY5NC<|1r} zaP0~>$^}i3Z+5i0-{JGrnxfNGzS4T!=DOB3(-0irm=NpP zUPK+j@L9N2>~&WJL+b^mP+$aWvYZY>wjJ~DG&^lK?u!C1P4o$+hma`dNl;x2gjk?D zw}wqZfx8mT4G5P&zox<$6cn6ywf{0kBGug&jRTZPBqK7Tcs>N+paiQx3V}%UWgiBG zM*B5=&>h948o!-P#&N^Y7DY}1Uh#i7H$kd0IqNwh&==g$^NAtCDNjK25X+|L9+w5L zP)d>?hRsaF|C1sV|8AYpLWSkidW^&cW27g(p_TvR?CR=@ur3A@XK?-%NQ4O+mlzi} z!MWxG9kva8=-~Nwu@{A$_4E4}TjY>X+-1I42WWjm2=eiTYIm(ux6y6&TnjwG;Xs=9 zN@21_lhKT(eNP+su(UH-@|R6@Hb?KEIbp2B8`aNV&g%P7=O978#su-}(WdDvJ!Wkb zG6~Cv%AlF;=;>V*3jRx9s%X!uCGMRSd3Lh#4Abr-a zP#VbRop@2hSht6xTQuJFVmPgp{_(Nrr_fNPQ}glRdtpc;P27HWS|Pp44cE!;J`YSg zeVc~OjU|P)2cW>w^-DRBK*hpQ5E zJ8dnvU{#4{3I0bLX?6Wu3KLIjDEcOZO+K1Zxok%J)ZX3Y@?UdvGkf~AY{Aqw)&Y=e zd-?ZM1ymmY_UzF_2m)Jj%WY+_G%9Kk$MhdFiipi8C(^pYkN0 zZqJm8o%PYvz>DClC^P8C_&2OsIOKKL>^RQ!v0jnXuOM4|9bFI%@)ce?y5m@-QZm4$ z*?+=IU_;tC3C$Bnz{$iu18J1zJNp1asBMX(F`kVPMDaUV({P#m-fnk1$7>|+V^Q+{ z4*1RxA==Msid=v9E|ysn$r%rZ`?w3SZw>e_YzMCdEsY2J_T&16n9#qX9qY<^3l;xA zl)VK|RokNn3@UEn1LbiHF-RtMC zx}qJ~J_}c!y68oa6Y!idh0=8pMx?GrePHT6YcZ^!eOuQ5#h9TXNxyY3cL(un&{~r+ zTigG|ngMZ=w;6m;o>?Db9nrP@r%<#T86E!XASl3*WAZk!ugyqCMkb-hpwvI&g@NH} zmhXTiwNZPhG%|husBxQz7l@jan zL^h_;6d?bBl+7^=$3JV8Z=sN>X0&tnaN88ST4Q(;i#)(L1)C26J~Ahxm;A z{wgj@f5^=l?pQko)|ZNF;13lt?_hJB+YsLyRHLtTANM)Me#~CjC9ceK=Xa8*!rt$J zS;8`Bk1tjG_;FiMoHWFT{VOPq3i@d>chZ5nKYN;{sHg~h3{wLTjxtb``Qg!?mMop~ zi;v;7a*>K4GIP=%2;I)`LnIr90hushcXcurwFE=93Zk&MCJOArEz^LW4;oE>zN37^ z4l0tt#QBQv!;?k>-m11woVIHS!9RZzL?O~D%-23)a;%k5E05nSZM z#QxYDohIk#lIb@VtA|})7R*TuA${+uF1qKy-R6~j2}rgpAkex@3-JjBZcoD^hk@}; z?b_@(W#k_%jz^~s$MF^u69`N@NqwM?W@*z z1y&C781GI6*J0iwnVDadF1tH$T9)sIx*U22&}>hqf~oN>axsmcQz~jrZlUeGv3QlF zP=1bXYhFvq)K>1XSM0`!uDpH&BF<(WzE~F=cZH_)&$V9`M0JJZ54y%ym6ky~Tle_9TC0lkrZ44TwL{BNvz9h^^+5503mwAgdior>+g#G-nV_v)j3(rs zwl)c1y|ss8K5fP2b|ttRYv)>{)hXp1utm<}zY4atOhoo&0A8id?iyEwqV90}R;^F> z&y4}NQt%nOmug1*ZrnhuNlRI4Vu$h8Jbgy%?~~IjQvYWy?g0OdgX#7NE$P3`R{Fxy zyYedJZ5hQHpUZ7mybx5Kc|vmp@L#FQ=@fm`;cfd*>>w)i9#ZG?<@SS-or#Ib%DoiY z4yz`9{|3%;XvZx~|3?1LWt%BWmC(*7ngM!L?5+7AJtoHcJr%}N1uhFep;FNh=(Z#+ zV>*S>p9AvZr6^5e9}ZoLJpj+HAu{V`?3B;z?m^*nUDzIon)Vzh!oF&#F;@ncggdQm zB4zBxj4;he;&M!wuATl`ceFsn&e{oX-8E7Wz0BGwFTR2rK&>R7gQ8KmPc(LpRpW>P zOI?&WAe71k_I007^al!z;AaCOX;5tI*DF@YY0i<6osG@A*L49F1I5YwpT|Ib(kU|p zxFzgye+FpG|Mb)0Iw3@WkjK1Ndk29BC7ed5Aob<5p_bcuf61v9e{cn8uC03V514(b z__LDEn2U#?9FBv(0pUdQy>G>zq=E8W(}Vut|N7#h-Z?sAF6<>kP|q*5 zDo0qJdafHny4Ihmp?LJ%z7l(#wsyQZ3%qQAX=+rWdfoin$@7J%wGS!spAD{6H{Wz~ zYFiuMYx;OekLhZ#@;l(SPyX=4Kw&QKMdq0HJIJeVQSR;}{?LSDfU}>D!cH*5&RwmM zbQi%i(QBij{Eb`&{j+I6Ew1@I#3u{10d%s~rNH#tCS9^ZMoq2sUg)#Oig0$vgc-=A zy@TlO(l(*aV#3!QeHAxYVzmJJ&_v|rL=5jCw2&9xmO6li`3d=5{FMcgI!OrL^$NT= zF>wMo-{fx+Yxck~dIsoqfcF1QfyIEvEc@^h{#V+v)X5L1SQiS{OZXd^mi7_T!VsGa z;)q^}qpu~!tg;JJXGCo#dv)n@9%+1eQTBu!j8Tzi&`?wiFq8o%VvpHWlQfnOCloE3 zjV?iMKyr}ZRn_AC)*s$HQx>-4ont5)3rnmaL3!VZ7H&)TgppJ8ZvR)W5>P%3ecMjd zf`O4InV=v2LmmwL9`^ru19mIGH1s(Rd1|wWz=o8 zTA!dduPeQ^n!mJm*ngX22>_sFK0e~4IP4F?#$Tb%yZ^jN4+Q)o&@=oi&1y~!!)3_> z?LYo0p!7JAQN0q(l_86u(dFOYh=PxDX$l5XAPe?BT>PK_wy&Ifl4z1x%!j+-i`LVK zl}4-VG83MYHJb1hKcBV3_u-7p?1>nH_oTesT2gsR`DUgW&S0>GFg9OCZiet}cD_gL zmq+Wy%RM;t=CvxAhW^p~04AR!^nXtYIDX`z`Yg3vq@$lH)335#v|2%r9AETW0DiUrbb5;KLa{R96J~G_`tZDxp!KciR*v7SJ6-z=nJ`E@5#NJyQ;e!x z&7|{$&ODwIuSs%ltdf|G*GDMgWTw;89M4<%miBA4&0CI#wZ$5qLa-B0D_m|2sHX-? zD)9d~+b+{r*VhgQa}ArTTZ>hp`Da^m3`2wX;bzYSB`c@hfij#}2C9Pq5#BntN_1NF z@5sq>0YQ4l|L^!*J3d#S*XH%Qa=U+i;o`-5^U zefQy_U_g7JOs-76&MEK&o;T=YIYJyUEOTf=uM#z!H*B)zq@o%mh5(BxW$GEL_yhmH z*ISk_s5GzE5OJ)%K3XOXM14$+ykp#0B(SC0ux@T!*(EPh%tj<4^m*H`yt#=3Rss1k zsW%P7AZ!%qK)itX5Lie8_k++$L7qidART|)+Qi1v(kGCy@G`b1E+u8LSR3Sqxi+V< zQh?Q1(fAHlLM8T6y)T56JQ#Eo<)TZQ6VP8BJ)iS#tS|ob~tp;Vd1-RkP_CS z;2OSwpy{9wWEjy?XwSa_cueM&^d)%jSsuE z^k54@Wq=X=)yHz$*6=y<-emqE4|7A#>l!W`?W*a77}uMl^x?((cgDXDY9O_1G_yFK z?wdy&yGC{=*`xO8cO*L{SKE^wx1P_bcT2d+O;Z!Vdt@}dkE?z|2Ka^UO6FRI+ro}1 zT;2Pd!vn?zo?4?`uOyw7LPC(I^wJxZP_5a0nR3yqabu*xJdJ_;p`6;GTmx>XCMr%zDNt$yAf z0}Q2n-EnK~5T*UU_waa?&=O2cG-$t&Z2)#_I)|Jl5GC|_8BM$_Np?RQE?~ha{rK%& zrsw>OVxBuq&iAvkFn(V#u%st{^>!2SCGr5+F0n&`FurPR0e^&Ew?O*c$=xJe|iW!pP1mq)iIo8eBK88^3N4ZT=S0|0Tq8$=O>KVK0k?OVMnV}smlM_mI4~1wx*=fs9vH~?l6P$USXUM#NAJzE}xYI zVAr{o&szR`H1rC^i}{~hTMokN;i2p(W{^seza&eU0|q*Jg?rEDsgbM#?fTv0i7d2n zL61-YZA5Z=fut}C`7MP8q7aJ^+9kUJQuZ@%Ny;EM%?eOFUx8o>H4edl^bPU@;qnu8 z_Wcu5Vl@KyqkBxl>0gn<(_XSyxv6obqL3J=MTw0pg?U%@y+@20Xj)-aKm;7UL_S+b z{nhDp1-53$2bCftta7ut^I*|fd4w)(%CrWGbejwN(1eZ;CQ$pn(XYPXdy>3Dw6!cvEiLV>W=&3vj;Q8;j|TiX7ggP+4-kOCpx8@*3*2)BM+ENcsfB^+lo=v z9Anri&BCXrniUpmfNaNz9eucLeVh-KEmZqnD4Dx(GFl55v37amQ3PeSti58~(J?bn z|0Nr5Rs-!*E($kqctX2%KB#F{Uqw+ui883&@80f7~s@(+8X)+W`w#@?Y{2rck&^?kRhAQc@fUBV1heN z!NtYJtM;79KwbG~j_Iof_tXB;l57P91^q)~ZzJimK#-cA9!97R_L$kmhmN}w`xnQ} z;BJur=FcE}dGQohtsMj#%rShPD+F=$w2Hja9gvb_c1zde_I;3d<*@gx$6cD6K8|;i zlfK$u>8H2v?YZA;;H!T!lJv>ueadm2wF9{4i=KCbfasR#JZQMUfL1H&O`Il2xaevl zsc5f3<>ev~VuRIURu53@$7YaGNfw?2*zPVIJ8+iGo9%;OzU|S?Wx6m3iV?aQsP46H zxhKkD4@NAXwy@lZ`vI0o&8*y$n909(fY#roLR~oUrR8{OGp(2k_vX)Y3yo>Oa@wR` z4P^?)_rV2quC&U|!|+Kce{jwKknOGT^FV+9)Mjj1R=}y^axQ6iyL~N7pSF0zY0s$@ zoySJo2_DA$d#!ROa$^cr`c^i~9Q@P!f^k#7Sfx11=G17S#4Si{8U=Ef91-*5`mo?p zAEGA}ku`kVVEEhaUpKy8e8`V%?|h4ic?D*Y!**EFBW{FV=#~Ae2{|K()SV-7Blf|8Q znDCkn@M7H!?c811n-a~A$5jg#SsSg;84AG_7RNVjJt%2fH!4z?$TPm&g}-EYeZBzC zf*n`7C`5|cLl7pP>+JUH_7e56(qL_=qOVSQo7FE2@nSfMyII|>E8h-h@V<-k{mwok zmmS4`ky{RGp4(JQb}P3%yIcF#o0eX>OIGC9bt^(4jxzT7PX%PQ1soSv99o@%3ODI% z8gFq~U6(H&wF&vF8zUnCgIefdR^;Ka@$vQf^fx`>?0h1Am+)%UTNcw>ReEl>gS*EE zPK28>6>Cyg|BjbgN(LNON)MsbqZA&jj;GP1;ciVuMh-OpE2PM@bDd@5x#{8g|A-56 z$&E7fCy6dr;cDfI6^$V{-#PxF(Q^(=w4&c>h;$|rocAYszL9=M)V#)CUzh&M7h7%3 zQ9y9)I6LtqbKYo>B6*TT*!)>hq*hbBK_Y9Z>O(eqGmD~h&n3%xu0zSK{TU8Ib!{NK z+KZ=QvJ3I}TsQV>l7e@b=>Ox_^P53b zhjHdBgc_4!k{}w8x2b(e(6Br2jS)#9#RHW{nb(3^4un#I8l8QO1bfP?4-)Rjf5 zW&G|}@AlFJygY$rr7w|j@NYa1ji}MG;?Eg1SkfiDSR%p{>6vtTcBC&hmcuSEiQhn) z&VL_{7eLd=ssF2-SonGK{lzSZ+FhAVlCYMi%<1Q1Cix@PKloJ1#_vgJN2#nN(+qBs z{M+d3=VsL{iuwIF7kxdt{rET-S`-0LFKT?ocA^xfNr=sBw-gm5)B=SW&`j>`v4oaM@4e?& zdGj^FkDv`K7~|YQ0`o^j zvOj<5=b#C{2d^t^4x10CV zRQ`r~QLR=o0cHo_4~jx=Nd-}~{PYP+E`SgQXew!1+rrz={7A2R0FO0MrhACDg)~;G zjo;1y@&!mpd`y~}oAnaI_UIAVA^QM!2K)Jda=@hhL$&JZ>HVUVe*h)MfNTZfd`iG% z#K~$h^j0UHgR63nCPeFtq03l-JcvJTa@bYjZx|RD;PUT65U(1dZ zO>MBT1kCd^igc1kzW- zdVeo7_JS$U+gpZR2LDQxrkf%V2Cfr@on4Nm@)nbmz;KUf}Q+)-_=hxe4@e{DrCCzuvzY|dU6VTM7-Cz)=_X}=}+z|XhaAvX%++dnH+@u@Ab zwd=0KTo)xIXd1b;AO7=mVBWDd z{en=RKTf>X8-;GeX2(2Bz~mIHM`vrJc^RuA_UPoQSSn{NpwWENp2O z>l$g%Suc|NT~73+(W`W)0I|(3O%Mwf$b#lgO-*@&c!09_^GB&p5JAYQz;~=Gcacri zlV5G!8$8X`a@zM#Hz3~KXD}Bl_{bGDwC;d)K$Op^MZZx{zH}0ES)Iwt8Uvkp_=9<7 z?e~(NJoamVA(Pe6WH>NTl$?4lH&?!ZW6chsDvb{N)=!hL8;Dsx_yVI9C9j6w22EP+kC7Po4;x(}t!9BL1AF!X@H5b%*H*OMUq z@$ks&<WsGp2V}`Opau z9zvm`ZrZ0xOCdE!(^UHJv2dSBulz6zr+dKQhapV`q8mS`62&B0b%CN z=SvQEdl{C6QDgtN8lchO{x4+p7q}u11XDVpat1#%CiTsrMIhb$tCt%y%97z8% zYh}PWm(##WLGe6J;XCFT=mC7%4^nx6E0`j&@BaSYj;>P$gh7_hqV@r8^;ZrUA#Z-w z^d=-~g2+45eJz9ZJK;Y>7r(B>o*#-vk#v1N&;fgN)NZ$IkjIDu#mmJdtSn`kA%q*6 z6*^UBVZX#Ho}dL_dH>pbP%&yYtk{mYTXYDP5h=<^BlI23A(1z+v1Al%4DsHdZ5?gA zgtuf!fSpRAt2!}qPvRa%1q!Nt|8-Ut*r=dKjswYcPjY4Q z%}4Y4_h;+u&U-}u>s$K7eF!=D(f^&q5a@scYAng4#s9R=_21HU)dTvL6jJNUqZ!A7 z1HzUfA|fvItA2h|(Qc9Q|5ug*ldH_lLzq6Krq8fqxh^7(9+tKJb4#y5>J=Gc= zEuXxKDUw}NZ&Rq&D53Cp*WONxZuk|fj~%DB-AkVTz1&ZxhB{}%2i zn)9ziLRbqw#@uCxf|x>~dQu@0-5Z_^fLqXnKkXiyU>Gr_w+IF%6lOgnBNhu`Fzdlp z;r+$5eiO;`;2g%)ct^1%!C9HJHHP{|Z;X#YJt5#voJFw;?V%7_&M*0#H%Gw!^q=RI zp3&tFcIOZva{|qmgbiwqFB7oHv8h~k{`dNYPVY5@O?#?^O}$?TPDA8w%~#{rkD0Pm2TY_TP^S{+IW8Le{+hWD6c9 zI6rTryocyeRz7vU)%SkkC-7hYN?}cpO0WkF`syN16qe;ljljp7FAU<{^z%PphptaH z?>ZjLnSNetiUdLTBkk;QEhw{e6Mt)K=>~Gb;}E*}!dOh9Y#}vVx&*|ifrFkN)0`DA zT8RYh6QyKMkb?eB7Gs`&lHTjaPS9>A!K7uGp4%|ekyL&9VQp16+a)xZRIF*TN~}!h zDVUseA8V+R25zGtdHo#Gm^yrp+D=6N7uyv=|B2@I=BC$xxjHF23;CNFx$xB;ukq5C zfWVo-!7_2a$kUY_Rk2R<4*;*;EkB8pFcoYnKt|J9UwcD1XSVc9xUuf* z-gMpPld%`bv^$etM+bavg0+W1u-CUgLE(m`+s)WJQ8a#%e`c07|DQu~Gg4A7;FjK_ z<&d@UjR|@8_Vo$k#Q$zYfb_FI&9a#i^sD}up2WM=JZyc5H#ZwjOcYs$HBO`UU15gM zy&zvGe^f+7B%DZ~1(Z&Mz)MCD@DGL&psn>1VS3}@;=t4EJF$@1F5bZJx#0g+ zJUAOLkmL=*QvWNs8m@ZYs6UT2Ud8I#yrV@x#d2{mFUAQWR*va|zDy&58()O2_&|;+ zs3PM&*yTQZ1gg`fvcdm)Xk-@7oFyDp! zl$ntYd;bY8n)xLf;5>hcQ@{UJ{M7GqM^s5EE!d3QAF5-{NYXLKVdv{UtfdP}FO4tLrKbCvMu z*VHO7?d2p<|0}CDE?1pd1cG!af0&8~507woaNSx@CiY*<5p<1$AquiL8FiXCfAxTS z+H>UBKh4uq@;*YqPGiKNojm&=jtPotZ~>BR@Ba@|b;6IoqTKK?)aE!zyRX4&I$LvoM33WFqm{Woi#(iJ=lc4c`!*6SA11U`e|8Mt&mmucw5Fok$ zt2N*t`nO;gdO`R2mdY1-`?^+2Aee^0jiT4yJ)9CN=Kx94Y!r&eu@o zpuwo4jWI}^%n}V90A=Mm7TEQQ)@jM_^xCy)?18@mNEC2iy^38z2n`EMVQJyGYDge~ zzucQ%%~ANy2&uJMZl4d75vGcE>ISQQ_5oa{X`1ztfDl>@6M_PWGVQV68 z%7S8kO!@aGsgkkDw}bH9ObyFKsT^rMja)gr8RzT0-DI7H{2%Wu^Yo6B<QgCZB%-@F`J)G4{ar_+SUeX_*!K!4B4qCH&2ezmG{B2xl$SHP! z0%kX*X-ADjyTptfeYwYLO4Fw>ajNTSIUmKE!CgO!B$nOHqZNoN+n0ddM)Y1zvP zqeXqjXQMn~M8B!j#fhgVV<|d=>As;edv@T~4Xn5+0K<`U_w zoKgqw9iqOD3r!-6k9UCzh%h+4Z7^@Tv`&qVP+vn#L2;_`t!m8nnJ{zPlS^?p+t6qVmspXUmRg*rmKu zqn4J!`n^;Xe>pBTw*Br`T}r%C*x|17DFdSKR)(ArPHgt~>+UUer`oXKWrg@GeKMw? z_U|jylGZ5SkfkCTlAw( zR`(VH?>>+(;@lO1l&an^m?3b%OD?_%7iDohGg%*JPXZT2ytY!K=z@fl z^dm(pd3EWcLK2lIn*Rhxt<^+v5;Nfu7*b2UgEcSUj14iPA;zm$g~yGU;6pqM z+A0wDPK@-RHzG9@lQeDXnR*dks(CZhXJ_2}33)itc~2f&Mo@&u(w*41I5$4KI|x#&qANiY_F< z!ekIA(XicX>Lz$KPT$K-U6u%->~t>LD>m9~vH-9C@2cZ8VBZ__vFcp+B)Va6d8-A^uchn3oI1+f6@+8|PE7C%29tOGZCT@A* z0hZPl<#wAgk-)~7BXN1YGtumJnR2GIG92)dh<~nNocDSy)@i}zwxn($*?mpi(b16~ zM+>L~anguJ!TwG2Rby^$?s)jPJd#SHchfruePC8RXy9Yw7i}@{ z12kjV4Gyy?rKP#^GBR3skCFl}^3k&FP*F>EUq4uv^2r`P`)!J%ch)k$c+0L)_IrJ2 zzVY&4{??Q}OZMf4QqoMVI!a4f_ZFbS0Tgq?Zrd`Qc4U zw(OEvdyYIjJ!2NZpZrCJ=8B8yS_t^ePOyiGe#9N(vK^|_MsD(^#JD}Tw`TExYXZla z2*>$y-f3Gg|4%^y!GN^hN)>e+3Bso=%EK*z+x-dq9!S~BJnNsDJi=S!^vV4kubwzb z=zM7yW#|;)=Gn`45AR;SgQU`alVsfQ+@>Gr6_IZ;!Ra|5g|%l_8=|MbVBeewZwI zychGjF?;C#ou16!>Nv|l5wEd{b&A~Pk^aVWW7Ap}|9I+KZV}`Rx3?7WW!>84_PFg$ zXc?XBdjFA$rCOZPU>auKY_WwT?4ltNZjJNk!d!6njfkI`N1fi1pCSAR2r9m~-nj3H zj7VzS@+CTuws@6yel>y1Yu1)b^yqRDeA_`E)5;F1H8TxENay*oCY2q?2j-nYp2+ft z9V#Zf4j!b-gW1Q`uJi);^V=Qp1{s=vShxVdWtg;X_=m%vXfDYIVXl<4tynn_|58&X1m`yzflL3N~jJVhXDK zDrQ-zGEd|%1uQ#=nspv$%*)6rC}>8}j^?bKY;8|~>kr&QgoW|)F$~{;^Z%X;=;pG4 zC()t_I7UD$HpgqBDYDT5`3_tbBc&@7kge4(lb;d>azw)gA**A;atU`SX{YOVOUX~+1U^VnHnN@GyQ1zlN>jSF5-Mhxxqy!BR_|LX( zjWnHeNP9xhNZ7ntZFzfns0*+bV0St_9TXTP!@^Er1d|LP`azMb3UC}1^69h7!y%uu(sqe}dkiHXJ8|J#x9>@JT{RlfM~ zCR9R^Vn>@MglROd_-135P1hptYU_N{47W05k~P=`Q&L#&`;-R`ST((yVq@vG00;3a zv!Y#^o9iNz(XS|US>6+y#YQU=t--qVB)!!a;VZ)7ZIbdy-=F1Ae&$I}+eCM8@e#$5 z?=Xn?#_X`6Stg^w>m;OQk%}k(r8w`G6sB2pwha=mNH{_&j{r!}1VM8AC;9QY>SGFbUZkC8p$s)0X$gmUs%vHaVc8O8AKkw1dqhThICKg-)uOJm zs3YZm*4DKMm$VV=DmUp#5HVka;}CKEF~9s6Fkvp>>ef3&NkqiAyP2B5(Vwc*sw1tg zQ*%hGzn=S9YTSClY@>Zr;M?U&hqmT4C#i^1I2Fwxx0g@{k3~dA6|OSH(_*>>g0iml zOZYsX`lJ2)0BQ?Gagm&1Q`k>Q;CTFnI~y&zOnr%n#xK2pzd2#DpR=RKXzU7jEyRm+ zO`dI!9ZJr`blk9+whLzR>_~o%R?mAszh^W#(d|O9JxX9`V3^aCKxeo28Clu&lxeTY z1SAqRL1um?Xm)837XTSiAel;1(skJ%r|ZES5Y4?!`XTnG0sj6K$Ca!x$;rv_@yd5W z)#8vuAH2-Gf;NSj>e+qZ`EUUXP<%Esd+N)6hRTrkx-5HOkl}PXAW&v7MDoCDAu;@c zd~vu~c87wvH`fq5A-)cFbgVhgP2p(^oPjV%54C5sZ_qda(aZH_kcgV63b{c?FiS4P zy?$-FY& zA8TTu6>wwL>`1cO9~MOH&0keRlu_zi7PgsroI3NUvbyZ7X^2lv{9Rhk2eBCJU(#Hd zAXeQ~-W?%wCX@ejVnB|=m)Q7WuVu7^XL0{!$c4%2jFaW}p4yFc&h@g7!b;C@ijfis z%<|btM=2z6EHYDB{60)F_BpifdGLJ0*JwQT-K!bW6toUvZhY-%S(PwY_6i@*kZFo} z@OoA#eksRfdfPMqN8TZ=0^Z3oR*g^Z&_jgVp@+pUzu&IBQza533w%kGF}rM*vtCD| zApIIBdKPd*BZF8?BJb?zCew7YO;1LCsAtFZUeOy0a0zi-s&|9f@*=plIj)t zbc5Yyi^pwT@K;r~5hFnxc@q&vxLbsZPjzXiF-@3~%0$7z!C(#p$o#JMS8cq^akKC4 zASp|!5)>%_B5M2mrl6p}r;+0^A4nz75aMC@YA{v-gJg$}l z{c&z$66iHYDgkU}IaYv9`vM6mTf))MTD^Y$03cL=at%$$^);U1)tJzQ0NSpYf)=z< zXOBpFe}u5J0gBcL_`qvxYYI9JR#pdKu@=%qG9mM& zy6qzpDDw!dX#nL6$vJEQZcLI4nzwocLDnXIG^4W)C_1wi)qyFCb}A0ZsQpa-YunUk zFD>=zl}FM#;2rO(cm%rp-z0T0z-OSN{suy;+tb0x7g_H!S#hSWHt1(u8j#)`*~#-# z4y(Rs#JXBv4GMvP1`nK7vGujJoNJdLscnZWJVywyaqT>YXM}M~k?NwkNiPPpSdC0X zewRJCU~pLfE90AwyB|(Kt;{ZI(6hgkd;U}|eRh#q#gloo+miKC5*j(h<8vd|W$&LMR-6p_MYd!^9Y&t{vbF#mb94(Ui_;OuO2ssY?q@ew|J3cy; z9bJ|(fipKl;}<)7=@F#5JqH&_fr!37H(MB*<+PVKR1K8DgtfX|v5!yXj0)J8nMbrn zizj!lGamUPY06SQC-1w4c8)a3Q~|4#c2ofWe7@o!pco7Knj{sA-dCGL|fZ?`5-XXwUaGbVHSyr)X zT$?=~d~mk1*$!j1$@BU*^&5dX)@nS6r>e5@!oN0y+spF=mY%1{t19>v<(rB=UF^|( z0b!zGHm@{#1s20~3p08VLY;ErV*opA5#y(P$a4efH@Hn2S%h zl>Fyoikl-BSz>p)=sBbB)bKr`a;+6GV0>>P`{;+uEoY5Xiq7lqjjbE^Yny}@ z$tfxGLyo~L=j2d$3bG*&WmSQffs6AVAwE!RF%!%>=8Mv(7BA9i6zoPt?0+06Vn;`h+F5N34j^pA`;ArCHk;iUB+9fifh&xbK z|nZUtj}5{I&bc;6vWJ18TXV-W3(ZL zW;Cd)tMlrzYG2Cg)JW#itht z+BjECY5uW#6@?r#HnAatTKQ7n$41ldjnDb)Ktv~m{5lOSu+~EWU~5(STET3YmE~Sf zOvP8XECUKd_tUp5pTkg<_3m3f|5_N@0&f#@(K-F>n%RTR@QO7~^5OnxMhsEy#@V-N z19bH!t$`6E7D~!baqOez*j_#X;O9Fbashpwd3-71ni+;faa)KAbzH!7lY4jHh|$E8 z+xP=V0UL?@BW6bG?`8jk6Gh*~L<~r{ezyx>nZz)HNV7{IR0c;T2 z1NE8AjJu_FJ}mT>2XH$EaR_nwQOYmRXEX%VrE%*2JROe&CWya}uewkG^Y7yhyn?q# zO!^LmZt9>+2g9+!UOV?ce-+^E1qP=%_`=;(?d#tmr;F|f*YcTi+ zD&hnv_<>6hJPJv!d+@SUlVHA@iD9l&vcn_%`&;+tG`G9{&%p}cJiKpRp$jGhZ<#!; zebxR2tX(%zz3Hhbf9<{pX@dJvPo;8tYvWFA-GbL)g_|`Z&H!~4EmzFMEt`p{wfnkJ?G(`lF3qU*RaXVDGaLq%k1n*3i zz$D=1d_D$Ey=Z{Q_4sz&CM<=ISzmv&z<`12r4r9}XwbT3lBYgFk0ks^(wC2V)sqrP#WNATl@ew)#kPS{7qXV3R^OL zdg)EMC4R$fZ973q1j*EHCRo;|D5?j@XdeM*45onBxnMd22V&Fj{oZxy@Wis4_3Sc^ zmMSCRx-PvhE9xwXRR$YZ#Mu?h(Ltb_)Ey}->axF zwa77|h$=-UCt2f~b4F`Xg9z>@9!^bx~nW}MPn2=U?sZB}8AM~1DJcSjnojc%6 z->qUhhrI2PYLlFjXz!H8`n2Br?C)oUQDRi3w6t1mx$ljq_4NhW;%}PVF0FE_D}iC# z#>5+(3seG5Icm6n{xwwhIOI7g zZ4Bfvu^Vj`PLnf=6RhW1gxI!!4({bpvq%${m2` zqIfcF-$R&12QF~vW4te31_G8JRW{4C0D^}WMMsm0#f|ilva=^>*MTsp^aPay5MxMA z4x|E(+YaEg0v7?GEG6ma3jXe}z>5rl7m>|RQNQVWyy3g*PO{sUj3#Q@KdL2Csn=q_ zQMi<9*^cD?M5uQ%`fuH7+dWAoOLeVAFuEI zess4!@q|QWy)>r(`{k4vXpOU=L`N>wCuiR0h~ZIUnV7IjV(zEd`%Nj5Ni_Iru@>yJ z>z>HXA3^Zrc*>mtNPtA@4Ph=|Hr+alyPMmf(_ui3FJ{fLND9cW*I+H~OwU+Wx8_Cp zOTJO!ntT(kF<}^|xdP?#hLfxYeaY8!c$K+hW;`gQWG?%0pGVOc2pg;LP{Kvm1Cbj) zKFm_1az8qi@(N=h?sm&MoF4Y>6~S~?c#MCFYn6b3$=LH@)ky5~_iFSQ{$dK2HnS6J zAW*?%^?~mhB61uHq<>?Y5yfpgH*utLPLle)VJMgwa2un327{A_bA^#6szvf&GsE~n zj)%M`Q@Rl259%qxl2CZ^KM%G>`q8#jTl*QGT1P4tj6f(Fof3g}_fa)}egHj1GbkZkLsrRczarBZgfvMoD-oZ$AcH`liAmudld zz2c^$uE#Y!W#P0&NZnw>r@jT!{qiwL_1UWCPT+56mbffL`J(-X2I|FohpV*AY++s< zM8oIr&oS!%x!Jwn?h3;_J#JlaJ8T95FB+Co&P94+0C!a}uf_nlj+B~uJKgI+FSa^s zrL0=e_qmcdSS`|;gXtR(d2EpT3nN%1hktID8HLQp(~uUeV>$2RL(h5Rm3C2AWt7Bu ziX`edZa9xBNB(a@7`{}_Z6DhPEdStyV_JFVAMJMes zdDEiFyb^BBgW{Ik=dh8y(y-DzWJs;IY7yRV21j^p*ZgrvQO)J4h{{E90gLUILRavT`BEDK2 zg=y_4xiOf&nD-ICkjs36E>-5Y8TRp{u2*hO4q!7!KppnII#NzK60k-jV_`GunS_Jm zMSmjq4OG%xUmT2xTUhJ?OVczNb<2oCZHf_=OA))PbQ zWD%-X7ML(kE)zc4l<1`+;_2Kad?L79q;He-_rNN}t}RgF?uYgfWIf zyO??Gjk+)mGW3rbn`k9QvZhfFK`_GcEe|zH8e7Y6aD#H(WKUr>lyvjF1(xyJjm7uD z>p!ur@D@8cGHu7xb&G+)$XeVS@9<}jLv2wqcET*=;xz~q`b{b}qCs)L_*xR(SjK&R z*{0s_@kfSxU5;HoJ`|pE8Cy$9Nztlt&q>`5-XF^B=o5wjgN5MuS_|l^^x!r`sEK0e z=IAw@sxXem=-LR09-W+S)^**^jm5Rig0%mZ^GU7TKLsd&ME+xz<*lNLU|Fs*3SUXH zc8b$-yT5V0S}mt()Kq-!+J0NCiTh57p1-CQN~;y{nJf;ZI)ctu2|H0tm$C~kGc1>V z&>fkQ;A*>o#5*|X@`WMX$;JL{^DGhXB|-S%_(Y~Ar>uV zrxYR(`wWZHyyAbjT19nj=1z^+V7HpPDk^JHFw!cp$(25)XFN7c*hMi|&zI>z&4w)~ z9hyb<&MFtcZSI#tDfhr??*DuK7d8ON4#r>8`SAwOp)Sr#l3u>qWZwa2p*?!ugu(4o zNN`mth_1Mp=>Yg*&)mCpFkt8REoUqY@Z4_bwLScr*7*OTE#oH0Vk55V5Z5uQgcj9q&DBMdcuzXywTr zeG}P4-Zn5FCqpC?$_nXcq)-0%n}w&v-DA8^dApQbM6yz(L3Ls~DDTAo^uQBLtO}!R z&*IP7-8?;v!L>6u0Fly+*<|BA8xnqH+$?6^z;ZALG7BFSI5M3+|39?7bzIb4+xClr zA|XnLz=epEfYRL|AU$-0bV@gh3IYNW1Jd2i07D}pAPn6d!hm#l?KN`U_kBJ4dG@>a z`}w?k{y{&$%rNttwa#^%-{UxSkM2W+*)=mw(mdWT_a8^YshFo61G9jP1$-aR`5!+v zKL33;@5fQYDep(t-3hv}q_ zgLKpj`bi^y8R~;uO6_@l2w)+kb%}gZc7L*>q5`BD;*zp`m`)8*dfb^$C=K=TlBc1e z8F#NC+X=)w*0FDJ0Uf;x9vm!wUxwjFz=$x{{NBD=R7kJ>N2FMV;JUx90mKiq-J`cr zeL-Nuq4H3QRJ z*S&g|f90~NRWF3`Ya%~?#%2WF8T%hp6HjC!XFQfEtVDALfg+%41ZqmHdqJN*X~OOF zcJQir(z?gM>~YXJj<%!%gPUK;$nbg}J4A%!%IW3_(>8YPHo7`BZ!`k&}T zTPLe8Ct$mym{}kSm5SS&ziBdV)N z^KXD*xWZbIk9=+LeV2ctf&8EboDRc6fWi1U2M{V5dH)_Em*X<+P*y3uG!wn`& zR(AV-3BYzQm_`xD_QcRQq=h8A?wdc@#Z8b0V5+LDZL6`EhidyIC~2@ z%!&B95#P2diW?LMClDpWYECG`q3Pu9dC;b73QVwi@Pl^dLM|Osf#Wn;7>v z8j-COj9I!X^j@m*YB+}Yu1nB@V)^b~ejk6p=hF?uQMlTbt)8LPnoLfXLI z+q^r#F0|#>511K?c}LfdxRm^W#E!a27biy2NFqRe4Ix z6tg`BmF?e?%oNI+I9;F0Q{(e`mX)6X+C@t0aBZ~2?HxF7s-~$IWbFbsfl>#M)&p_K zvq#soF3LXz2QzvXQmFzO`aj2%)kH4y#Lm!gi?M7~cIBvcnVTQ!^BB1NPGWkX>&`k= zm#z?#7ux|?19Jm1^%7~X2)^*dtJ`h;easnt1*A+|N&%jVZ{U%@&Y2s1eX2r>)&k$5 z8Ol+s@;N%9@EDzLA+M-enW0vsXJ-)|(8_s$M_l9-U-L-(lM>BDv^R&IV<0{3@4v}2 zugB8K#^za_9WOA~Fc&#)PC%nUw#Ya*mnkfu3O2G(V7)bhUB3;*43Fs`o~2ke9zi)u z>Q3l8kBZdpWqra-fLkmhA)S!%zTIfP(<(>avD+67nTm>`QW-H4S^Hw5$wT12inJSK z;u@9qSlmJ>__-kE1MY1*dO(8~XdR9Ri5X#)C14cLNg=loHMLQX6RvVn^egV+j`=Q- ztNBaeqRO}xlgXUXLj3U8BC%o;-@&EV)}VW`MS2#PO_n-rMU&Zujz#2MW651`C-#%x zV+HYy{-3hzTqpMJhZWyzcCo{l3RP>FU?n#R+7H#>U!fyNM<{aE&Q+kh z!2i|LDD8rW+dmB77NiY?S4b-F^XLJac;xRa8CR3M;uprIGYe1%O&9Czarxkq!otqhQuNO5}2L&b%7p`e$1D*aTvhY z53ATXdrxkGf5$AVFo*5QCyb{A`*5mGyaGCwg1Bsgr3F;X5+ko1sKRFV)M-V8PFjm>470&I`VTJ^tjTQq7BlUU0RgTl193 z$UQ8_aKVNF`b4?ZRg)KT=kV#?$M9pvn#5g>gkv|mW+mS*B;*Vf!;Lr37)4|jFBDX( zCJ-}2pW)!8ca&b4o! zOLaDN{kZ60hKZj7y7t$fF0^CfYD*BC_8YYI1+0d012O+z&COt1aeCDMkj(!6dD&KO z`~Dt8UUXjhJa2~?OmCC_Er0qi5CwGJdaMkt!@PWl&h#ASWZ*j8*Dq0dzmTZX2DC6P zzlwKlPQ%20gqEjLKxdWF;r9e+1YH)NEJJ(iQ|GT>dRg>{=z>3=J+QPC%$z5-rMP=& zgFO&ipE%U~THS4WREes`S7ylbeiRSb06A=@f`P-1&>vidV(Eefh87%TT0zrhS=5t* zn9H5)JjJC7E5%)nm`aZ(HE^Wx6K(_40CT`Xk3|Eh;zYdEPoHLQkEKe0jh&QqyhdCn zNg;;-8rU*FwmW$UDx+OW=3C#gGPd3g4MFrHg%?~_^`u*VrMSJrNA+{IDNVsd6)dvQ zY`_#zq~v_=kA0i@+QWO0h+<50TrUoi`EueFtp^{k&w+e`c*YRKe8d4sy0MvN(voqc6I33%3Z220WJETuOpMq^^e6V8nbV{YU>&d&uZ&)c_M6ii74k;rkA zWXkQi_u^DF?ChMDioqF9%Lx?AJ@q^${S@z|_G_=+oeR$L^wQP1SB5$DF{SrOjam-R zkED#+y3~9~sGZ$z-%1c%j;yF3Y>P`wtg?GwogMm%%lEXVop!@*_>=tv!!s}yk`7lJ z3(vlq!zSfdrM7J|#)g@KN4z}2rNJR+g7mX#7#g6{2i4D7c2;HFL=&Ng%5EHZB6jV$ zDK3f5KkI8xD{N@b%v45oZ=P?!ZQCPzA1Mmy9r8qYKYsjp!gzod^hVZySY}CrS52BZ zW$>X!le)LJx30Htw_j_&AE$L&`;ZmL7oT?9rxx_u2h(oq0X|4^zrAK}*;1=C9ZL~EUnUN*wg9z z&5%~Mvz8Tr*ndxggK&``R_5#)X1g^7TNaN)Qmeh|K5c7?b`3~oR#maB;?aG4sQA|z z#ZNeVJoX>0*mU@xUK$);^LOjj{>q4NhH2|zN$}pl8xvS|C=s)ExzU1p76F>K1xNgs zLn1ylx8I8hJuQ3oHL4N*3V~jD$#gmB3R5fas+u%(KlZ7owOlTq20}{>jlpK#;pp-! z!dp4;8b0B-j0+GuZ;2MJr1+qC0R=pI2^sma69nA3_;t=?`2jqGEaV+G{fTQZ1mW#- zWNugBXLums=FsC|zH8!h_i)3vH&if15LtB9O&78nSQjF27~iL3aC*dvS`T$4qYYL2 zsCSu@_yxHvM3S`2qQ!{s$waomKQL!;yg^60(}*uj zO6#hYttUc6fyVZ*1x=!(qsrHm;}}?Ea0h^BgPT+ig-Ce#KdL|&V;K(f7@Q_YFhh6M zcb#0vVvg;onO?}aVydgW;Ej(D|D1eqnHWK_Y7w1Y@T4Wu#EwwEvoZ>lihvQwTGWcW zB!*VljPHVaT`jx7AJklcUigP@dk}tE%x>1D+33y7oJ(+F-RzGJ{1Kp%)5u0G!l|;B zz+-#7ld;)!RUF4|o*$w&MU0Q1F}el~V$20113*o)#nrR_gE%9~1OaUZ>KCB{&%`HIfFGFb*uCwzC&L(lahc+jng3YTbU~ zcYZczo2ry(!70dg8{a(?PdRsfrdB`aXA}^>;qYU_F~3L zB)>!+ChA?h!NA9;GKVxV2pNCL5UQf>z~(BJu@=*KxI3lAN+Q00Q{yn?3F{mIgdkEw zuGy17YwPja4YP}slWT_BYDUuN0R_PyjvBeA;d4gUpfC7QwNM$y?0sVb`V}G`lM3d&EkuJr6XFV+>pEk6NSk>A1K?7Ivzq@NmG6X;!K2H|cj+ zHED<7-`8_=bj&BAA|>_E$l(L?&3)|pTuPcQPveiWZD#6u4W6A^Sj3VHfbK?PgSOAv z2HfxI_(}P};b3HuM!FmZ+pC!a{`uGQPM-&S@H|)rfO#c2H+Y35r|s@9 zE_ir&h$mB~4Xy;>`uXc0Q0UnSdG7!A@TB8Wu*S$Z+PIv7_j?MmXl%_ixNXfST1R*> zT|p6dXWbVgIV?&SdRv_9r6)xuN9L<^P-BtV>D_>0qou0}Fz;lFOU-$8LcgQS zsn{Wac?{OU2MYGS8i$-WB&xieUo+kE+l{jcmD*cWZIPRDG*E!%feq&X=MZd{iJdVi zw+RTyCTm@+P3#81k{>tE3HoK&j|5Wvt}cD-wkCkn;t$ZhG*<~&D~jX3TR?===-q@9 z#SBu4Ei8bEJDgGy?QyTNHGO=I5E>zW5O3uN$Pi5*p-*IP4_J4SO14r7ImqDgZ9c+T z9QqF&)AXs%2NlULe1h{h9PI89RX22R?th*ZwkJJ1yy`yrg&<5RWPCC5jKpns87fT? zhPOpZG@jdC-rIO~y4K6E`Z2EYWbqbztawECN0krTT*0~>FZxu>j-pgNCLYmGN5aZ!jCo_u| zT-1`x!c>1Whnwm#)4ZD}3`hG^+s*l`n)lYslQ|dALzLz3_7QZGwfB1D`TO*!zvwC` z;jEU)B~2ISbJe} z!EKoEMXg#>^G?2!Typ2pG{U7uu>Q*yry>`%?3E`U^+1x2HzMP9^ZF(0DC29@ z_KKCm?FDhlH1`S}gtlns!K%J^cVK{GeU|#mUBv>8^9ok(!u}A&!y*%BkF3-R>;#0% zWgwN%+0GWb@vDWLGq>>J#XRf?!A%c(^`ux23$K@7TUBV{E1g_+MC-TkbAq=8b6Qf$ zhweSN;S+fn&uIcfD-FhRlpi0$=Db@do~ z`2bmqO+W~gydsV!U{TZ5*TjguRd*>GxBYIi6x0GDtsscuzryD%)f2_a83CYv$ldN2 zpm@ZWw7cR6ro`TPKM&a5v>kAgshgew+PFXC)$qyLR+G4xn1x;=6dZ}>ADU^AQQN`! zX=vVu67TB8*i6@Q^6-4c9DkydI3hXe0G3gZX2aOk)%ALEK_^hkLMxw?l5(>|Ui z8>;jxtY7cER6hmggBNs$chjZ8Fd%gK}Xa4a|Q^Xs0FM^N>_ef@YYe?~I?R+((ydC`=6UKj6~IQVKR~mW`kzrT4Zj-`>87er6g1J!Wa` z)CwwRLscXghE_dJid+0`pGnw$1xZtGdX3_QuALmRv)c%yDSPLZO^wd}e?rus%02Pk zN>-&2TkcVW@qr;RnzS;!URXnt13Qk)BncAoHwz$xnj> zQt2rDghQ3dGfJwn)!@KQ8>&w8aw6IGVoft^h(8uvfTUd2Gi@5yYKDQqjr%9fQnVr4 zW3bB^sCh7E{_tPa>L&B-hdH%uJV=2)Y-m|`l22@Rl6;xN#0RpgOBtV8LG@o-TWaSQ z+{2#{s(B&yyi7(#Cv2W7!k#{>Fq5Y$_0@|}s#nPmzs*dZc58R*9-rRb)Az2nW+FGw z+_n(g@^JsXt6`@4icIjSZjO1}OTfD4r?^~x2yh#FBx=rn^YP^Vk~=3$QxkC_t2>+1 z3b6BLd`7Q-yLqjK&cw$aB)&bdJr zr$EaOW9QhYt8%eUeA+XS=b@+Wd-%F4fPH!*<2^w%>o(>8K%P&d$4@t$*pv%$zqj|$ z3DhmpD3bG=i;#GMh=S_w>u%x^PFYP%*6vMjrdot^mH3Rt#M@Y32B}_7a7aixG}jt- zq`7Ldv)QIQHxo_piobOBne~!AYng$+uSu%}l(Ifn1L1xEJuE~yZM~ICcXD@ve-{PGdr(V+}ydXBvIH4*x>_QY1N) zgaXQJFi(|EJ#H7rwcDJOd>l030;znDw_9*wR;c&8VjaYH0)N8~+3(-KTa^DqZ^&|} zn+9XX5;(yysf4dzUnWQ+POaI6gp!9}e*^$5M4O=$3={($yn(`U0fm}tY&;@9-t+R@ z!?oQqD;EuE0~&QWgI1MU2DrMv{#XFW z(1O?Ushhq8YLe2uhNzmF8sP3RLTXTKJUJ)rc+LI+faLyrE(D;I`p)$7>^{Yqhee4h zGV2Xy1U`Mez}9@V@YO`6XZOUfQOUbE!!sGMvpy|cLvTp*&uqVVgIs0F7zfdy(v0q3 zZ=VHmk7iHVlwv5W5DPe@i{!x*+8Z%7uqFflp|~+{(<7RuozE%o@l`$H4!y+`(&4^l zTbF)7-*|Zd9Vmau6ETqazvR0+^?npTmx-o2cQUC9(5rRUs{bu6+f(`7&s^)SlLtqI z(;Y64VWi{ky{pFQm3LJiV->49C`m~zgV|ROJcWKY^=3@axJd*qS~C+NLS5peq@~w} z_)eWu6a>XBuV#F9#piC9RVdQ>;DXV0<@L1+4Qm#f=X(k%g8-95&>2VLSObVQdRH)G z>C(DeTooOde+|?0NP#u{mJJmUz|oZSfnVEAu^t@C%-LakT(%RCcNKAXRi%A%Cwm4L zc6(d0E0xqD17@AJ$z+1`{QO=>=~>0E&#Bj&0#%$0yekr`m>nWjW**4vXwob;EewcE zen+%3{Y+6Wk!>vC@}h)79N@e$o8><=H=u!y6o@~J^}B*du2>99_S-}g&j4{?A}E^he26>OAl@V9hT!A`=It!Km^0rAV)2>gUw9 zhgf+SxubJ^W_qDu*{kbuF~6gt;<(C0}UNEL_dYhRPs%V2x$#&}{c_WqRdQ`Q=VYw- zee&!+k}>$e!0&}+r$p3O=`@bNfxclmXiXI_KmS<}U9zEI??=WgtoTO)?%M!E+|ru^ zzKc3!jLtJ6END1rN-;BsLcyQLC@h@PiI0z;cONS7ZvfLUR3xi_v0?Vli{Zdq+A~y; zObiLF&?>4@{VKqMs(wgFRh~!!duAHs;0+LM`COXa)<%@i&xh~&)K1ZFQ^J{I**Wza zS7XB)XO4T~ai+-vTZ7ECuGo8J2nwYqR)f_(a#)W4EZsqHLzgfW?%9##ym?J2%NPIM zgVT$xJ0Z-I{KvMLTD;0N^of=q3#=@3EQoVdsrdN|SCObDP?k z6UP-CcKd1K5KeV|ky0V!z{6_zcX^PPq{-Awq`&rw7C|FP>C+~F{ahr?YMKVyoR#P!)7 zEQ6G(UsIRlWE&XwOk5O_g&1~J{^#CkDW6h}yQrF@q7Z9pAQc+g99g?lOj92~JxD|>Y%L}3&slsVN0Evw2NO`3{BM5U zW@O4~tHjqOOKEYw(Sk$A)m>WfP#I6A2eA@&LeZQ8|IZ z-~=R51G|ZCqP#nr#GAu{tnpRNAVG!sq2voz*6_ORlHR>~A)X%wQhGn2;~F=DP0mW2 zEsUc810W?z#!SH!3wo*gzgFD;5@3rDc-$j)D_82ajKXac@%?35ld;jxgsTAK?Uj|Dq8Q@N+IIaoR00fYK_^r#uF$$nN##oDwkpd0ESx8<_HF@=266x7tJS~L z)V|W$(J?Q>eEysptL>%dWS__~!x*nvEiFGz4vnf0po6uz@n+0d2r8 zmzrDbiW;mzF7EnrubyBWn0$qO=RMn(o&R{CVG?^lN1J#3x)AEt-{@%w63lSsLC zCiKdH8MA7}G4mUL7_W~rW@z;H8@h*|N5k`K>t2<_Z|MC^I)58>En0Nfz>_=Ax1(om zX~HXjmOo2SYg3B3?nYu@UlTas@ll?J>!&eCJYa^QTmM7#Wn{1zK&um<{Jj2YhpfMD zy?gr?eUUzZ>v;w^kcNQk$)Y%{YFScl&wPOlgk5D zs$oMt?r0TQFuc!8K*m4`urYRGqrL5XUqN~^mSs%o7k=77GqpPebfwWA;HNaKAP8{@ zNv<|WeV($|WNHjMjN3-W`47MNbRQEKCQI=Efn1ua`f*B}ru~%+!qH3MPOvvL*#AHq z=4(S^o?*CP)*%(q634Lb&4O-?gO)Fd3P=H*I-4uC9}+x<12ezF+*<@PI4mst4Q#yL zW}IyV%LiwEj>Z%{`U5^dXcs=b%6-4 zljyB-JYry$H3#cwx0w^rvH?8>j=lJ#qar0ij1_YK@#OF5(0SO0XaE9LvJx=YIQ~`jnh9AyA7fD`)WOzbs2-tY<7My zd^bd2@1QNg+6lr-kkWMGN5!nox4afr=_$nRw^`wK2|vD?@!dm`s_L|^WMu9?v)$mQ z^4ht86RUdfE`=O9TYj5(D2XFk#dWzuOzd}YIOH|idQt%5yTCrp1bI1Hr@G|hlzEUE zm-N0@C(O3O%Gkbf?S-l9yO#_!S3io2!ahmi3KK`Vbh>z#uw{sJ_HziDZ%!Y%0vrp= z4ilKQHTTX|KA8!&I&~aozF*!w$SGP)6O7L}-;pfV{#b1G*1FwP0R6F`;ItjQ1c0$X z;pem(BH?jJRWGoesrLX#1&E+NnGd7_U5^o~i>-)#4!56QsKLv6xs+fJCJL#`%A=m` z+sB}<&B}M_y)W#xrd|q+2D|HCw(JXzQhnS%qerQ-cHE@v#TYi<=#hZ6ly{VK zs79eN@<$Q}kX5{*d{I3i`O$e`z%kq^@#~vX&E^8R8lz@gW#cw*&U+b&CkLGTx%IAR z{J}$A526FNL^<0QCd_h)9Bjem+!}oZn8s=%-3#jk=#>Unk^0$~NqB>vVP`Z$v>9v{ zWm}xA3gwDk>iPO210Z~b0zyKKK)kd`+|l^EaGMLR(Y17HAm@Kl(ZqRCvSr|re+A3P zUVP1z=bg*FwFi%#I@>?zY)UE8W6xCVyoqZnV ziX!8*s<0ll1CI8O4c%_DK1X06tlUSN@-NeNvq1g?Rvu2W?)SJtxx-c$!+)O(s*M@x zY}J8gGQ>iGURN@gm(-MeZe5VGgcEeDokZ7M75Cg2`HEnf4YtZ*!Lu5{?^#u6hcL(? zZfNNFHqJ0rA8g!P;%BRSq^Fq_a1jWA-Rx^I6TG=hS&nx1mJUAjDPhM@J)nxu$u`RV zYZgPAbo7+m-Z9n`lW`J7z2D8GpWJ%c~?46|q;U^an+fvnig4GJ&9ihJwO zi8@5Oa<)6EK<^_>k2Q=dRbotGog|%qBxW?UcWI9;+2!nSD@a6>bWTF1^|CcW7yno+ z(QabS)=ToUYHaT5*l;DFO04O^@M)^8bl&F$>WgNc>uLt4);VTi1G0XEo9YjIt1a$m zgsA2H;s)d~9q7gp@cwn%!zm%|=H(cX#ear_262Bw_G})DXz_)4~ck2=0Uj{`;$0^jIr;Xkk|;_%5qOJ0-~EcQTQ85q zq8(VZEA}Ttbx=H)%5SgK+N|#T(xH8QeWdpFhR;cU_;0a*B9NMO2jbK}XaaWDfeLL8 zsTaJ79-zb!-`tW~5c}c-PEJilvn8Wz(kx&1LHh@HZPuV*cz5$NsF==crY*}fW19FK zz&0^UQS~ZCNYHC75JU+%WK(xqRkBcUyMlAu_(PZ6B&ucH31Gz$D%OgK;r4Yh1U1+O zKUWss#CNdEhk7p5fvUQ-2PQ$%E)-mrs^JF(51)=+EIlIbUJcsal`ha9 zD~QiB$33^Z6G~%yco7H;6K`3oaA`olgT4e2%>`21%+^MPqX)`Y#>y^0Z%~fhzk>7| z)xSd^|C)u37o2YSW5_0S$nO-zb6DcpL3_#6y^!l2Swyrl69=P>AOm13X^kaUhwYRS zBe9}}4Zq2-B=E68RysY&qE~|_rl~9GdD&5)M_tJD=)`w(8~rokPKCVl!D+g|mf`}2 z?Rgfu0tVTA-vaJGXt+1u=zdFB^uk^4M4?j}X3wQp$rF#Bq9gng#0rs`icdd+R3_Js zA1rN8%}?lk8eEI8m+u$pytJokEil+ro{TiXQwe#TDQ_00S!S$OI}zBase z{_^bflmE-+&Re=7`>~)_bntlx?Afyx^?yw_NSNC5f+Tq}81JL|reuE>w-V49YHM$2 z!dKG;D-39QHq04-Q(CI z^eOhEH=X(uaa>51!x(w}K$?Rm=3$O^nH19pc25WtWE7kKjKHzzg_NK)&WI6N^ z7yS^4Hv@cvW$_xAGKyR$KyhztH|{@7A^@;d^D3-{h?<$6bp@#4qxGvTY}HzYvf5{vOhFI%`)E`Nr>E=2;i<_E=xp3U9JnFj<&OIZ!59i zz0%g{%af*DvKLAeY7M0qJaW72ntlh91FVB+7AO!S(?>4p|S_eth?5ALlz0nIxNpv5sXAV@h&>; zVGAjwkMau=$$yJvMQzLY)b|76ddP7s@a z#U6H7uFafno_Sgo?ES0~2z^QI=|WH#*1VSP0GK|Ol6T*`kV>ze(g@Nbq2GatRm`1S zviNl-{Y<%DQMSAN!ICHURP9PZ_|hv%%VePB~#kRLQi1dWnBjjlb{X{@bOeoct zUk7GZ9IfyVl`Yp-8{;nz-p5_>%8EF?dzBs=+R?6JE0S8R&r2Fd<|spBdP&Zw&ehBt z%Ax|NBkFw)qI!E08-Bl^adYVpbQiwzP+UCVZoSRhB2kb#I&3?!2;cIHvWZ0Hotk58 zp6ze53;Uf-d}?odrZ=70+o##Ua?%~dzDz{H55%R`n+zMy19nJuGVfOggqxL!CRpB z^!JAsYcb48oV$571&vnjXVVdhBa4fgN#jvDjoa&AkKmN`8$m%YL}Q-6sCG|aRC9-! zC24H=0M8n?cEv9y-sa_3Y2-KIs4l5pjo#x2qzHJ!)OvMNc_ySIq*9#w^ zayD^l)nHO#-WMVUn=hR{Yl9f_L_|chcwAcDrITq4e@;82pFyet5K=H=&l3swES&j} zI{N?jutNSyKQPz4uTROfnXW*DcGnnPR(BBuV1$K)u#<&yBu7R%f{DDPrO!j9#5uk1Qv@Nvf5s>dVdMR~XBBzKrafieQ>b~QS}4+zA?_Fzf4@WFauN--g;Yy4}5 z#rIO-8{VGD(nH95WO@=fQM|^TLvi*Ee3m;?_uiDbSV^=Z7u~}j$$;}V@33F4oXOUT zaQG6|^3d*}rc5*~$9Ee~*S7PHkeCbmr5{8u+K#^}>6pm|kn{v{)*{=q=??8qcfRe6 zsZ-eJrdg6OW+%JV*56UaxA~OYtZfJe59!2Ar0IIC$5arI^+-N^B1=G787Ri&$*}Gp z_8qZDuwJjM-p;&DaB{*}poSfGKZ}0G?3v$$&+cfud9jV}=$Hm^*TyuAzYZ^9n1RBp zHdK3&eyr9?;`h{vrFzZ;HG07l209v=-i@7YjiwY_*~RW1D~ps}nhsY+4Zn!G>=d5Z zU+QY13U#tGSTGn2@O~$fAw=CN!YBhnS4W5PwW;Mcp3E$j%9LHY1DFuaTerN>FVl&JaZh&L^U&6-5+-F6d|8l&S^@hccY=2)wfE2pqJKY~ivyD2Ar!dW9LyAWf<+N(t9G>lK5 zkHUGW_itW8Ho2ihTywMk*0LizdQ&*oF0?zrZhfGgAaf*mCXla9dXYj_&?zk4J)tRy zxKNq9w4l;SaC47$yzz+3Sg@n^t0%A6OD)f|-Y2{^E22gA_6a3U1BjKO`eL?QHwUT1 zS+tlx-TbKXYk_KWaf#ipdRQMb>!);M+u7Qs@YT!#d_tA8XTFse7qxLZbOSlK8)!0` zp7vu|4mG-9dZ0bX9`V2VkvqgJb{(|2E4Djl#4d?Cy1KCM-v>Nf=cTSdN#|s_B6AFyI{3l+n~Aktfp9W0q{{n?d(sl}9ysStWv+>;;cLhK@m?}Yh6|LL>U}4EKD4QPPcDGK&o+EPY!4hxK z3LFm~{T>2rOiUGci5ZI@Zc zB`Dbtq>9^hT?m6&Ut50qeWbo0la|x9D=c}`h9=O*t+P{AN+QIhyxj?@0 z@(@ z4_kXWBqqEoUt9l5_B~yh7#D5=vCnylj<^mOBB)3zZcSf|hvY}CJ+oX(or>;ez469J zXiSKj@qNJuJeKSV1E_ZR+tnZyYrbDGDUmAMpOzfab%KdvXf`Mm~g_CPjL;$+hT7`Mqh zGnssdt%39K=jNkqByvVBT!ZBeR@MbHtu}q@-Kuw_Ycf66Q>lCA78W_vl=%#xah7Fx z_WVz5R{j)V4f0t|DRX=1EWr^D%)gaS;v2S6r=0#fdPYf7&39tTZD=vxww9}|aU?2S zIF$g(_^%ME+^#VfV#a2fGy-(Az*O)8m{FiUAfUEX*0{QV_a9z4L=X`e5^X0Y=Fdif zW?^Cxwa8z3-DMIX)QGc$>9Hw$mmeeX`GIYJW9)oXWAzE~N481ae=!B7>V0Vi_bO)0 za#GVp{_I3xFXB{+v)lvVCMr?^q>Sf)O14M`p8XItgIgLpu5=(1DGS0KDxS(+<7;Yi zJ^L?qWZuM+&380Sx>tm)8neagj$Y&qa*}EH!+qAGI1A6?-W`4AVX~&XZm_c&Jd1z6 zID)Mx-#_Mz%!hM(!*(weo3?yMza1?L>TvTg3D@^W!$+rPs;0S_G%jY-H6B;i+gl@I z>TT}rc6c(Dfc^@4_Iw-zJu+iKhxeC9OM&$yJltdi7NjP8MDFx7B{pHLte!w zVp#1K?cwult0)R+0g_R(KCWb=tfcbupGTet1f4d*nzB{b=BFa@tLYqJo{aA}qudZP z;$xGov^`gi;!!-p*>dRoRGJHxheCR(=q0^R&e3&7(pwFrj2n+%^y(z-IvcZrH$K@vTpgLU^zhTD0=;7;L ztO!({*Ak}SHZspsao*i~Ip+DYcNw#|%Le|XeJ#>=yyCK(77+UtA;}fb?h&|(F5kOf zM4H~MDCpt@PpI9mHQm2;ao1&Q#C-0Ps_V?63y+GT;}X5wHo8xZ)K-}XtI+w6H672U z>ii((;Dgu6vfZP%+XL=+Ir~r3I2duJW0SdjSwy0P;?Rj@CO)P&$6=2VGdwfRu=itF zp3(ioSyTP`q}<;aMJ%5}Q6JoI$Zr6!@PwiS+#e#I&y-CM|Hm`w-eYi}q;yq&0}7Zz zb!!zBywIGS92ko(PSwuF&?Y7{du`WXdP=(e7pjAU(UcPX3!|lE;b(^x zf1g5a5IFk8=#%*!_UC=+%k(2*4u>h=LS+$6HRMEH6{(K*@XQfC^YxEI(L`Jx`{g#I z3-asGqIb3@*w3UV#i}x>nzlGacAI6Sv0bnRW=H|sau+-WgwqK0l4A#$wn!~6E=KzX zxE9L_rD!b-{A@|JAtMvCBTL3Jw)mOMnx&3#TOQ$GbX9>wIPw}OsYcs(Lraz{fA4+; zEst8=U08;0tpJ(q<`*EA?;Pm~1EsRz3JlTk(!EQhWI`k=KbMFVL(fF^=VD$Ma|`5H*SUug#LtYXW=p zewkzMmyf~e?;T9ngA43HasYbK6z+QDB70nm-Y!m7iR^>`;G8EX^%NJty9jses74uB zUnVf2S+a|uqYUl2Bw!1^@rhzgw-wi{QZ^w?8*my*H~s}of+o++2mGZq!BkxZ$y^H` z{$|FuL)f~k&)c7&xR*b-|G3|`{~c%v5!6^;2O|3QPy87KV}FV)sUHBF_!ZR zm83t)Z9nD0)w*`ETVuzLY)N}C>;l%i<{C4GrMH6WV1gS&je^hl_}Bmhbm@a|btq|U zH-Ipr5A^alZ|XARwX4$OYa~o=C@3g^FqLnhrEv|i)SWZ3+g8av4OS#-@-}YIPJvS- zT%F1$Ev74({V6{ncq5AZwlB{JSp|=k-g(^(o!aTu3U3#QC{a-^otgVZo1UBI@W!zk z9q4ti7!-RFdKSa}RLE;EmT8Qvz3I#)B?yarMU7MU@R!{9i6N#8cKRGT_ebEZ_z9xV zT1VIH5rXw1Y3fMj(s;B{GS_w+!ewUcclaf*gxOX9m*orU&qjlGA{_xduMl?}J8q+2 zWuqnPx805!7N_C}AQjah^#U+Q(yBLEaMo)}ka2&5n?-*-1(11tcO;~x2i2zVpQ#1#DG0~^hx;v4D- z-@A)y#%nmle&<`EE>p;H8pgr}KH%->2QBJ%p^}l1#+3A5N7w}}t3E4bnjGerJCPE6IxbNOG=#0){JYxdDm@Dmx z%KYFua!d(m6uzXB*=fbVu%yJu$Z{hjKZnqEGrwgX$Yhg~V~)RYh{ysdY?%jD%kW-s z&01gLM1RAzna|$Q^N#wyF-|@Z-^TCYn6#0fEm^g<5J6+f9Cc}r-Ov_?Z;-R$d@swz zGw#{LPE8l(vJ9TttS<+8wFL*11G6ufm_l3_mM3Hzk7v2v=&{sQ|6b~74JPX?2cOpI zd2AA7F-5nR@2LDfI0Z<(4+!>cz$(-%OMlq`2>Io%IPs5=xW-2SnZ1im(pO^8w7`#agL!AZm6zdCnsDYdL}++8ciby* zG_e&WjI-|m1Ko<$!BA8A@j_4|#ycvLrQK5CJI-$TslrGMtWR~CfzOd$(VHq*g!h%3e+0GZoEKHUh+QY&M%$!ZK^zHgGwOtc>*=KL6p62U!lEjnt{Ms&rpe&D!UZ zo*r`qvN9q7r@OPWbI2k%I`dN{!;0RH-z|Fa%O#~J$X-ipX;rREtWitQ=DV;?iU%Z< zo#NSt4!7v688d@% zV+qjsG7}!Q1XF={IpdF3hu1Rh^(rUFW!+1!!QnrA1O)9ux~MiS`wiR_zcL`o5SxXL zqs&_=N6JApmN1a@)W~J*3NDl6guVn>AhZ3=4Iwmq#2328$}&_`RAx@yoj@AemR(ka zSGW^-{{%6LG4lDpD0}OusM~Pee=I}SwH?_Eg9F$JkR%u`@XKv6%${7vIkO|nK`7+kH>VJ+@{!# z`*qjHWx)T2X6h-nfOkWzl+dZ;k~sjl|LGRT)?wRMh9j7fB7Lxf@o);sldW1jt4anH zM=DaKpHX#R##1&%f$|u{okxzJ-FT|hWj5zND;SZ$0M-TYBS#ES89I?YRmKCafu`DF zgO-IeMB+abMWj%qqR0UD#sDrAP)#mgq*0l^e^A`)^!Q&i8bVaA+3!c-y8w!dl-N%3 z1c#dPCv9oVR-Pwqqe8Cys@4XEwZ6Qtn4_-jV=G3k4ePT_Q_38LL$;>Uv?h|I)Wn%> z&F~yNYHz;R4-zyf!{6%R+b7TW$G@7q8dj9xLcT+#$LoT*wAo?e8DUm|Jv_w)#L6tq z&emh3IZu`+dr@ZGt?)HVB4$FlmyP3-Sx$G{YI8e2I8_)rRjUvlJfq0sk=goJU6j=x zD)&lzS0?VXCv4Qm(D4bs6<1X$z2uv=n~pwCxO5%QK$_hb+X=o&x}347k|ZQu?ZKyl zTrfWYRssMB9}#v?za{JF=m6>&XzaSGf&w^)Z4TvxGHVwut>>gvX%s27Q+B2AaT}Rt z)^+_=R7A-EIo8P<9<#^A5on{`f-<2EcM>qF0|U0Ex(k>BL6K*(12sQvm2dtNpjLWN z>`gmu(?(p#Ow6wMhz{yT22q8$COlT|t?WUsZFX6i>Ql(>JgjTdRbw}|r%`2Nu?!v1 zFQvbl(*oHgEeQyq100(@_Y0q;Ib0nrP=$s|@_1gIeX(IVI6T~bRSFj1iPDo1;JQo+ zHwB#B5+Md8XVIwj2Y6z*`uJFZU%NiU`C<^A`#>9XTWyu!9c{Dh`mO@7l`*b&)bTrt zrpkZH3dMh$vVq#5d@(Ym+*rl+a?p^l#{OVv3W-muQfg3e)W?e?w8n@XFoL=k`!>uZZ*d!$dI>0dL4FDP9E z7N`JeO~E!!Z@CFqM-H>hN3}k+LnTu*gDTQ@rbHsZm!JpI*+79p59g5PMf&Gv3RE0J zM*n77Uj^#L#DLdoB??R8C}{EuieDbB(u^z9MKX~Y{o{K06qvWfV`5}vge+G9k6XNe z*G&+#AvVH}VV3%qu;~ysCnxdG%`HEslUJOmn5`j4NNMzD-Yxxs%pj7I-ohzP1cVdT z;p`9`)gtiKcpZS`x1EORah?1vFK8vXz@}tt2XVVh!xtkKCXJ5mS|1hg`r1ukEEt0)r$kcRQ&Dm9j{B+t-Zr!i?+UI? zU*%8KdPOD{QW6b}Gb70F_;0Y~{g;lqcDMqu%FGs3k6a}Zw!LNI{DKAo@MZTHNvrI% zb}A6Mo)_p!_N7` zyNSvtCU!Q+R7fGD%jk`PY=VpUP4=?#JIV<0q7l@)%mt>8+idToZ%wRcJw@INI%Js{ zIX}cKKSoNnuXbZnQ&agpE_l!8eE=Yxre;O{k1OtCK3y3f{n|ablf&Q0kqyjH;(GlW z#Pq&0DxRetd3Rrs+AFl{8-MRnS_nWF=Jq)(TS-*!@DZ|_+kcSS6VleC6HyKnqfm&Q zCPB-}{nTiST(W)~2q&}n#dAfyC47rs7RBNK$KQkz%&KN-U0YF}r!G+FE zC)|J$rVS8S;AKCffQUBo4Y*w+FSv0gEYN&v&vVC_^%gfLVsXvmV>q4_qc3wU+Qw-1 z{#XA?>c6e4r%dD_tG)*_sShFf-#>AS*D_l@iXLAd1&6--HkO3dU)c$3oR7ig4?2vR zO;`C%8R1q>@mIe4XS6-(X>WJF&VU9*YWnmeToLopx^jFan_kwEOh_K$B}0=iGK&PE zKc;#2C$8w+y}Z1X1l z*gJ4_R-?*&dA1w`+T2(UG=uKo^-4m5JmRNKt>dnX<9UoygUM_Ga3H$rU;F^22443$ z4{nB>eNLnS!zAU3c4nr$a;n8be1NBJ$z4m*Uyl4I3(ChrJqeTd` z>iHcuhUe#X^IzcL*mp5h^MZJed!$5R@vbLXF^2T8y0g#z$=YWdZBElRVqx5vya&TI z4n%F=o?|1u5Ok4xdll6m)D^JjTaZ1lliJJ7ZDnU zyku&>Mir;*RQBxs--|vBD-*)c}y`~QAtTa@w5!@0bWIc!*1_~ zB+k0a{wcf%?PL-rzBuY4uC6sh>mL{Y`AmlPo?V@ zcbMCj+#s-HCEFvew8y1nvbnxI{qV75>ZfK<4Ce@z?=0EF1Wi2JUdgZ=1-GLnw%}KY zJer$Hba{gS7pmRo7q+4ABCrX^h&WRl2Ziq>M|3)O);4Y0Ci0NQ3Q_okMg97siB34p zg_L~G(|!=Mze@#UjeG!sFe=5>_kdXn$`!o9(wh+*VUSng(q~_AynUv56`TqEQnuT%fhA zS*K+GJMgMuO}g);q_$k&fc+mc_u(w$?R;)G9EAg4I1P^rnO1;`B__{3yQ0l&r?)-j z+I_jzqQkFoG%ZwJu+{6UXk=>j!=%@Ubm#1&#A^IWTXqp=BvIDl3+~>CSFtpLneKu1 zuObE8R^>n1>fzMQpEzqzou+6Vc%@w3bS#CPl6QNZUSd0zH?n`&V0E=S-`eGmlYFL0 z=Gn6WeO%nXQl{L5)75Y>l=%IvW~KAhOO<479AUi}DID|55!k!!W!}j;Eym4#gjojn z?B%vt5OwAjP7#^U-y-3sg5`-E|2n3dy#0(6`Zts~zwACFCvun#{y8r<{qj5#h*xiv zSag;is^mXBRAP+HooJTy8patPq6p6-$C)Ioj&B#6)pR(vs`cgv}Ik+I?} z<}}~@xCBu#K4g^x6VtE$lB!riA(ci-s?ePKV=1gAg9xh|E3jHQR=S5&EJaxvCAB7) zsMSfI(7qON*#`aT798bFtv_L;)DAFQiI$o_G=!7|`X&TB(NJx?5nE{x-FkhfVnQ`A^+b(P!i?G~)`9?-fgm^p{mC{k*XZqglG9c9rGTe1I5= zO34Ha&~MRIurE;>N|((d#vW|aM|Ygc=iY*uz=2&n)+-B?flowotb9YnxfJ7p07 zXl9fV9j28e<4*G+L{h+UE0%X`wOlGiC4FF{+pxR|M`AI{%hP0H!zG;kEF+9p`Sp_G zcvhfM?65bpN@|JvUkzO~81Jap5b6NxKv4aP1rrM}Da}jOlxT4= zcpNq=Ku)_qiEHjz(K>~=E$0g$tdbNL-*?Veh?fhS1qVjU{?t1dz}HJarNhQ(W$5M| z1KotPHGI?Oua}yuPiN8_l~_KDi;m{DT|(8w1MNqL(mCdTT92v?)p4Uyv9V{(XE|kM zw#Fk8ulmG9ML|@w(^U|(Je-cQjYu6dvcpbCH5=SrOdH%4r^?!wmx4-z6Tz=fzQd6d-CYCsL(GHI-S+L+M1IVK9X%PxO$YRP?2K89VQ;^(Y)x>L~1K` zj3W0A4v{{NVepZSsU$l=YanEDiE1F)cbBI-M9=|-z&{%7`)N=umEY7+&Bs{hp%C>` zayG%TCQql)EoxmK*$q@C^-7o0`g-C&KR>?~U%6hMje{$c((`lOqF8rQiidk}rHoE> z))_aC!X-yZ{zA)B{rc(+PDqtHtv^IM={_4#39lNrLb@`TXKva9$i^kVN6EEYGfQm*`~FL@!{g*ukyR- zg7{IB;&9A&cC2hE&VK^3r#)T$Wx1DES0W#1E*{%FkX>Eiv#h?X&$1$?qEeNZoc8{; zN5pQPt6@^!YM#hy5-zcPIz$`z=f}W@@rg;15?M=Ud+G9tT^X5_p8O>CA++y-k&?>m z77dZWY}rNs#)5GSj?(b=lh^!*5uH(Il2^BV$i9%*tn)l-KOM$vgZs9DEnFed_s4+i2SbVQ|ZcJEq)1k{4>SyBqgLfw?R8NWaT!yi*joBxn z%SXu;`UXJcCV9_hS0aa#s>Z%QW<4NNBCAs&G<3^Y;xx&5J8jc1wn3+2C9 zD@d{prD!2J=#0j|U7k8}t+VaRSEk5u>14m(F|}gT$svF}e1I3-ki->!)g`#kKJh^x zBNB2mHRh2VnwkcS#Jo#Nbnvo1BB2qcsMbreZmQg6!orizB6$#9LSmam6&FMLw5ymD zM+A|rdA17`$%9gqTKri$y}AsA&48Rn&vg{nIhgZ>?n>kTMU7aXp zM1+pjH~SF3Eh`+_j$MY!#}}3X(5l~sDKRAuAX{UxG~_p^s$EA=G`^l|e5T1BI@{$n zhdt-SHb|G_wFM=~=ay}W(7FPJ+Hb2>9pI!7#ij2kD#eLUde}MZHRts5=}|rW6g>Kc zQfK>JeU1IKOS{;_nU4cPLv#Adm_1%;&{_`~DhJ1M<|V3f4gpZ?j+k1Hrs*o>qPzT(=59ijUT7Q$$+OZcYRjQ?D57m!vuH; zPo#)2re?=Kh131lWN>@#8`)!HA>hrk95zjMKj`4ut-Bu8w36nxCFkMcNqHQt*s$7m zv^8-!`peS9#Khr)!?5>=!Aq!A(s+R?+|5!}N>)~u`$Javz=sr2dU-BjC=6y}^){aa zXbdEe?~j0}O7{r9|KGXbeprodC`UPXt`56`Q!b-BS?r3ktJBkl;L%MRgzM=J z$V2zk2wJmahbtPG0@mPHW8C{qVJ;h>&b*3P1B(_-8Z2|fbfnhy=IXGIb)X-3&OT%E zS&wNu9dCJ>fx-juIs$h~=Tq_R)Ad0|a3GWk_WeyX7Lay&8`k7wA~`u1`*wucymS4+ zcbt%zs%q#`Xw$ZKW=3O#*Zpj7U8;zJL5jQM%9aS}(Z)>A$I%Ij2u0%ATIuexbs^H{_0}V@Vkpyd zY*M?{zH2qdjkjyEIz6Q4XNp6sW0q5`nF^bA`pK_;><+c`U0rRPk&zIU+dD+?3vHTK zJd;uVLD47+j~>!-HyEI^@)I*_NoRFC$^0>cpSZPAyG53PXEm9?W24mg^=ZK_J9=Wt z*4rAF-f{l9I)U?nH+J|Qdo4);7R?nTbKE{+3~Gmq0)JNcgM%NsXn*a|+wo&{MIRPt{Cg== z9De;K*x8z(c^^rw#?xSncpCWrMr-v;NqJmyCXVKQ;p+VvOf`ZkP6}qIe3z6tJS2WzBTEdE#sfxUHtD@ZoqqHWdNkMAgU!g@7W*6 zS{CM|p=OW&Hc8yt)|MF;C~?cS_ltzcD(FYhF)>faC>aO<3_DXCTLtE5;6-O z%@{sAQH$QA(+bsE;eOq17wD8$3dr5(%pr+>EppD0!=R zZelklA7NItKDl+FA-FD=flmCNY@RAoqiBO#+zcpVhswe#Hu6os=~1X z{QHVZN=&t=i0@Vj-0=4ca*uG#8K+PT5+g`cHf#ALT;3(=4rDow4;{=L1zuIg*|4rx znm{Y-clS%-#m;NJCuw!=(FY=>B-&M?6hB^mP^c2?BXH(L{mAzQ^W5pV_}{u ziz-vSD~YQJdg)(`CQsfLh!mf=ZHs zk{sC6?JElZ2awxvxsfa|0R|SP?-lL&kGcV53(%Refr6KIO=0)^4@&8HfMT`LAHU9w?u=*9%yH1gDg_ONgcdF zfc|3^>z?`a4|)3aHeW$jryjy{73&+Nw4n&P|IdcJ&Rrnx+q)iFy{}VX8FL5vWCN>> z4)d?%7DAo$Gk}<|o=IX4@6ire&H)0@J7(xj)oB4F@=rtvEJ ztAXPQ6&exGlGANnOXKZc%(5{-s|WS3I=A$d^4$aLjqWr4`AyW~TaiR`IDGd(LP4}J znxs-sHp3+UG`zZ1uEGCd)K~4P!sWxwUw)$M=vCczt+%2ucye=m#E#ACzIQFihikf! z1KNYUx)-un7#!mx8>qnI4jMd@QfFh>3=}_m-_RdaMaL)m`u%M3m(f98{Wbp3ccWlab;!o8xj+%M`<#3J4KQ&If#Zp># zW4bEc&kvBH3%$^Tpf-&QU?(Vz=9cVznl3Ssm3uY(CfXY+{4kDGn zE4qX5$8&)SSHK;pM8&~T-OlzCDD?oG&`U{4X|BN|uNe^HfT$#OPnrZ^7ytt~A|fKl zoavHiAfG~9e;4R1_&hFH86?>r-*y6+CsNW~-%&X6$Ld+s7eBuXSQE)7=j2<^Jr%gz zD*xlrt0WjVpRZgHxvuZ+<6{OY6TZb3#fhuzJTKVRcDWaW@c|S@ZEtcSR2*PgdPHPH zdpbJA5AF0k{Bp8e_0NyI$fPMFe(hJ(UNtO0X)&!P^nvJd;T_gCU4aTi?Rs`XcIbsb zlFaEv;45$dmRw^i0JRF~ANQ2(E=i{VC*Ne5(UXb;)x8399`QFljG*ryzvSukikLqy zNareuQ~aGbf`8-B;su70Zq}t-tIwiX#yv$ov6IH>S#2}9JpbqO-;Nly_UmQN z1OAcEN>-Ivw#3`FI)2C8b91b)Iz3y4Wxx-$l)y}VZWwVe>zyKiD17944JaKAz_f!5Rt1s-a2=#J7*OIgA1?u*EX9RSckTgvSYFBxbU(^*p&U&0G>e`707Sd%4XdE5U27pgt{y7)0V%_iZ?xTsALh7l%Q^QCnRa(R-OKXhoMgTV_79FpS>0E> zIbhnXW;=94^dzT#xVv13gRppZ&!h2M{jOdwMzc2fM}gDM$Wm3?;BT5AT=b7JWsL+# zlCQ^Z8zx-*;A!2cx?2&g{A>n~RCLsTY~*%FRQ!)3i^NRr+o}dISoO1E5{opZ=dP#0 z1u-d{Hh((YreqU2_nnj7Sz6*bDhODO`~S!vK9(JXmGd|Q$8)7jkw&S0s|gLv5;gz9 z1g9zK4gt_gG%ywzIPna5h?Ja|>sG`!W?M)*ZvE z{$C>4Cth4E9b5^nS3hsWNY^G#iy1?YcBD$rZX5V){Z_l>cXYG-xoEis3+NVZwvNiW zZb*<{^3TeEkZ?^_i{TPWEqkRU5ubc{0}Wwy0!Xn-)X-En=d>PR=nl=s1J7e+-IOj< z-N}+2*RMY84g$^hJwQRiSp|oc>WkDorHyfZee8N2DB=Th%@9XP$4Up?N9!QM{Lrt5 zad65V`}7hd)G20aJYaa|Fn<~J%=K?nH4`#uu5;7&h+WomCl2)BB$jz!%)3|u(K@ug zOgIkZ6CH@sz65G5>&(a36lD4gr%5te_0`WB5uncak{bX> z>hO~VocB`G)-3?t`0F5dZtri5lwr8e?fGi7!sOM4CN_=5%?Bq(LBZxib?gd_?b5l% zjHTx>9IBZAcbP|Q(wy~6h2@M!5$&9MGzt92hqfmP*NwFt_kKnRBN2tSLO|`P&3zud zgM@Ugg{9~^DtygKi;I|hS^siQxOe$$dfHR>WIvS{Ue%)L-AUqChFuiz6AB*uUw!Eq zG7H0^s8i=pm0Hm!cjj$V`^-T#8keH6D?(i*Mxa zKUP5lUe9@%N#x!$k4a3eC>O6?tSd<)4aU0&h&5;uveHtcEojn=DKQEJ` zKxi?7xUqcVIm{m_1q`#LQWQ#HvowrKVP6fPM7w8{BlizGf;Q@c9`i@%i1QZHb)xXV_FxEH2rsjq_Zp6pAK2 zef*bOGc%Jw>OZyeYm6Wg|8n5{Y-4vcs*2M6K5=9@=yuM8=q9oOepgH8z%vJ-(D>;t zM2UztYI5@!DW*$aq!91yasosa?k^4cF`Oy__8&!diSNn>q@mLa6Q1Vj|b*#c3biQK?>WFU$w|8bEf z^7$QjyCFV`L3^u^dNyHRcf|x_95uiC)jIq@M^v!>@knWWL8&(UalnnwhDMCC=4B1@uERq zuW{^haCTbG%c0c=92jJ+^n@$chAz|{Gwqc834Vq!MgcP60lC%T_T|Jn<-$sLF{uS- z3@b_ui}2~Y_wRyU#mGIR9VwR}G1FtY)T=x{@XsYm)oo`kNs7dYrA|*og^-XCkNsMz z)Y*^o6_9EiM4kn(&>5lp8jYUUAWUi(5HvwmbK}@`&Ndt{cqx=*?DC$Bd_2a+!a|#* zPGjbwOQ;IWuRobI1ElobhD*kRT}>o3~kPAs+D2A6>nuOc2j zhUL5~k1ke)%^EAFO3?B?{af$1AvaXz*a9||9aM}cUyywsKtE^m7R&U1)`z56X zl}Z^424bg*rZ8Ga-GO#;$9cQfqFR7uf?hvDD;=|t1hgCl2vsZRs-V9-H zj&iu(EEp<2tae>mN6?&D2Y1RWOxfzg#b91mMP)R92WgC;VSFV2^NUW=OKOEzKZPcY zNkwSyn^Pya71dl+-!IUtE*>xj#RRAcuYkZUQ1?*7lK&$|uh&qXIA5F$_&BLX2G;Ej z^0eBk|3%Wgr9m=?3bqd(bfm`%&hsGyj{5^h0(qn2!AF)VBB-a^m2ym;d__1M9_zAG zIJ0bXm>-+FM;NB}Sm5el2JSU+(LqFPmOy_@q&7kryp(4B$ooZId~=hT)PRlNv(nw- zvyVd@yOwH)qSuqb8^5%3RF(r~w}zDx#^_QGwA-D6|4Kx_XM8J8TU3q2q~6}_-d0)7 z$t=p`3@wLSrPfD;oo@#tE}^s)u5Td|9E7LzAA1p2qD{rg3lwj;D&)dQ3r4+~H1;x$Qr zkWr0WER?xAnKyUJl5(5P3+}pQR;xzz)kLdXu_tU6_}TN{o>jfDDEj3|rWWwBVh0tT;D`WiDhL`3o*Cn2VCY%5$Ag~Mn>gCUC_H!i>$wAO%!_sv z(ev#eA@YtUS&>RX&=_dIRQrdvgL5+-=XwpjjCCatvaZfyvjLL=+4+J5jg}-iZ}s#1 z2UVDh2%w2y_FZMa_jB3q7#uUHfn*VM(An77`1rJGXj$rEUVVJ0nORx5h<`GZz;~f6 z4;t{e5iN04(((3gwVNhd&~aqnPj4DsB0;-(MBwv_V%{Tmq2o^g*Z3)r#?6Z5|3odN z3HpbmQ@bWIp5IiayKBAL&ni~MQleGFt(p zcmM-HCYroXrzNcyyT{pCSpkEsp&udn)pWOU`PP%}$BK#|)8ieHG;-_QitFw)M~?O< zeQITPpiCN}Snd{vX;Bae3e{hU5TyKe#ea}gsT*OO$@sKsDS<2Y|el)+z|{rXp5a;6*xXq^^t z-Z9yGk;K~?TM{1f3XioqoLaEC(&$g`fl2WxKlfW(oPFZYkmnL&BUi-oXX)7za8Z9! z{28WvIRX%Ruk@Kvi4syQe#5W0#iHp-t?Z$7B;*u8YfEJjZq_CL8$zr3RlWhioz(*( z^A1m;ogJA%ooab&hdw+eEy_(HOV(5tpq&Tyr5VN?W}7$iZ)`eaNqPH1iTMIW@#tNp z!x?RPt@)2Dm{qX2DFLCEhEKk6ch*ht@s4CiQoWvPbt;+d>n# ziax&!JTRnVfpi9(e1NwVNuBMfB0ZOTx7#z1+=-TxIQF>gHNuyLs+P$(#NQjsgg?ET zg)*7x+aDgCEm`d=7hUzX$_mW{PYuofaU=?AT2|JKV7bX7b4i_Xib#gE7gpoZflpoysKc_3 z_?%@<>2%+hA_K*5ydQ(?x8Xlm;5K&d+r<68>Ae9{+3B~`p>=dhJNGO4mQNi_ zKjcxruqvMzU*LNtjZE^XtaYlctn>i)SJo{;8-&)#i0aOGDcQ0De+uR^wr_^({E8$_ zJR)}(@h_Pi#wI6EdUa?n4PN z|AO3HH5_M0wg(=Sxn4{C0gGa5<(YWf7*S&5=&J!24MOASkbc#cH|E(9!4yFPn}@E_ zhLW)4R{hfWZo`BgZg8W}W@F2dNs>U|G#e5u4|-%162+UEnmUbc%~lq$bB%1}TNoP$ zial6O{q_zcp?nUv2@Inmpa6gZol%O1N>}Hdk&zLOIrnn*J1xSjgnagE-`f`T=ImHA zZ93E3RgjNzDpB%kkI~Zda;X&IKw0;S{i)Q6riDdO*K&G!Bp?e`S5;M2RP?_G@>fkw zqMkvtJ8unvRv6lF_UGKBpPn`qmsn7J6RS|3q#UH^Esu2%XZLaLtztA@&g^P9`?Gr# z>*m&#lvpnw&bfoS_}pyKO<8PTUgP1!`4hrqh2I0#=uUxtNv!^h&HFiraYCl|LO5hC z_8Fr`*up++ycwXFG*+Xj94ra28|;6@-oN+KX#rGjQoJ^xz@N0VC?H)j`I4iQvFVJ& z?NHyqb;OPW;^VLV`ZHq_1ak4^NivF$-b-!oM~UvBji#)exB7W~c#H#_(HId^1E1|5 zIZhQ-4-XGp*S+l0fa;|G1ooNBa||LyM-fpfsx>ThQG|U;-sP_5wsqzck~r{P_>+1F zCUE-jE4E}vmUw%6gE?Y?{fM!4i@BCB1J!>T1V1PAYys&%l%rN)4&pNv_7P+M|i$%$YXF$*i|{MQd;yyY?GCCm)IEWi`$}`_7!|^^YVM`aGpE>nh{qN+w6F z9AU6o%-iFKtOtu?B$)T_(ntK6iX7N_tC3(;zq|s5C*(QH?`@S7vJcf$o12>j1qEJ* zS12l}MBH5O%vd6RNct~w5sfy^foB5E{5*n6cBOSVg-t|ZV`F1_c8Bj-eQ^F@r7?W16jC(vYAF8_qcWxI0hj{z59M_*r#6gv_>%2XzwD@FAl=f#uS z*tmY~q4=1{Nc6!@#rH3GGw77EpE1x8y!eKvo3Dc-;&@G_SJY$v0&`wprT{ z*_}0-<5lsTUn_y7H*$Lx(Tm?W2T;T{8VDNmXmT2?fD-Yw zJ4yKt6bxo+&!2C?rI-8w7iZ3Kiec{DaYGpSwvV(ri1ElbC&wEs9Y&iCtEa9(`?-x& zs?E1W&oOqq!Gp&jAAjLvYE6yn{-0Kk8T%oJF>NuS(Mc)(tGyO*U}FH_22k&T*BziO zgIri3P=t|c>S$<8I`|{M*`HtamYe^Ggjnoek;Fv6Jw`7&uhs)-jdWt{*i}90 z^h-8>%@d9<9)Z<-yfy>4jP;0Sob%CR)lx$HviR-~MwR;_=UP;og+$<;>&6m>j2Ti9 zc^9R=tZHZoKyHXWi+f=iDR~WL6&3ai0P~ASZ_S-F=CLp7&!0DBc<=3RQ^&DRv>a;v z&c9zSm=i^kOnwgNh6lT=E;ouUJ0#rqK48_}cRR&C{1D3L*s50fFbRLw)1W){4V-`P zqnMaj@V@u@P)_4PL>AbTyEiqT3{F$_ny@FNxalv0u8>gTI^c&0=y+%{5lpxAWQI3- z9f@WPjFS{N1J6!l1GUaJRyUjPfsXUiW@O`~4<-qqi|~G;jWH;dWD4J@M`OxW^T!5j z8w5qXv$({M_#%3DlmU;J_u(vs8b{%cRPpSK_c6L#-zfI$e51C~cx&$aOgGKuuFKRt z6tC%1qD%=|lC@t+p>b{HCpL;&@_X?-p5|8I9>-I%J12PV^9A8k7IDP zv?juKIXUF>C<+2okL4R=iwB|y77MH|JyMRREpMAw8al6VNOe(5I2>!Rci-7I@GE-o z^)gK~vi}B#M}CiZm71a==`KQoawLjP1ANF4Q6qio#>R`oB+3k|tpl;rNdwTWOO8X8moRxr z&zM_a^Xd9L&^ZI=_Ny#t)`t1dl*`grn>rb zsTgf&sM*Z4stA=Uq~GvbGCf#J)q6nwdnQmh`ZTu{ndE2423ry z6@ax(l=CQnPlMo_De3yr10pXp7t=y1O23tQwC}2PU8PHx8xD*A1NlD>-Wbl~Bqf~^Al~!n5GUJArB z9;Uy$eGj3Il~pfzf7ts_TF zO+}GR<=ywqiRog|yr0Zgd>WBz9Z2sssa+Rd{G3F0t#vL)h=@G}oZP=YPSCR#97iYf zzE}|fCo>=NH|8OPXfaS7&yAKIK#G>GW12GWy1hAV+%-ELv~Jq!09VF0<%>XcdfPeJ zDj7|`U%`eYV03-8D$uxbqNz^gps6{vAi5ifOSjUK$PBKcSwZmP!oq14MbHJ=b=ynM z0u$|0i!F+4V7$2gH=;SMAM`bhde3mo?D&CThw33F=nXr8^1czb+!H2nPF&JuqF%H2 zeoHm`JPGPV)&-QF*|Bk=t|8Bz%Pw!+Sk9;VZ?e;t@xF(t-4ge0fO##=4NBu zvT}GDw;?;8ICRS1vi8LdY}vYvj(@;)m4H0plzmmUW)Fz3uIF?L`8=as>)lq9YGI zh36V|u6C+5G&Eqhhj^W*@j<(FhcY&OQ-Hee=hbO|+(M#LinEz}YKG@pMIM|Zy$yPw zuK^xwTDf*?-M;v0M_{1f_5y)1vtyTSb$(|Sn8^B7uX%dWkK*yb!VKYTOdrVYS{|hA zJqAI+zAYOQdM+-A5mThE)g;kdIi-&SVBnmeNo#8-&B0G7My278??S#gqk_6S1mgEb zocXb!r-?~2HA-b6r#!0U8!Ji8my`*2y_=S-SEh)8M`I5@I(G|b>iM+4c_a(8(k*T( zb*_&mQ{*&G`w5MbtIX2}oA3Jbd5tvk?YHE3UQf52cGFZ(BaQa-)Ywh%%)85v34yS zA0R+uiW3o1Q^QT_#l9EytqK5R*@qi;9c!n~^fLxH5mL7x@2~OXw@x)+Anintiv9%Cts zQqh_TJWJuJdfnTnH@e3yn9^0WSqdAge@lXIRZ#Wjs+_93)vluQ&s85KN_G5pnYfo& zi$k+x-Es%sVBR~TGg9`)K5%Q(_*5*58Vb8v7r`rtTfXEh$TG(&$XU;v`njxgd@d5m zPrdg>BC$|W@h)sZ8fB=Hr$JNcd*5W-0C~hW$C0cOBUB$?JcxawrQ;NrQ8NXwS}IyCCGM`xtou*9bLOk6Qw4?Jr`J_qM~*^ zoj-yOa<#ih2S_@@hc>L_7Zh{DRK*-b&&tDC;QYu>vd;X9bHhdm55 zQ*qBbr7e+==y05&YzU+bF3=W7f0{Y-DhAl+FAmPJO|F(s8j;>0OV2g#@S;R6Bf#ak zy@3|^G^Ik*A+*}@0sI$vS6uu%Xm)GA+ zjlq>LY?2HZfe88B*wlT$z?cc;;6}bde%Qi?FoGmVzsaZQ2axZ|ub~nH{HHvzl_iMO zRNaAekx60iF2>q!OWA8y_uuC1aL_pi!UnI4p%jp)0j%@c$j0yY-{KAqY;-DWYU5Go zNG^>6z1PLjy7@aoAc~1H2>*4U9mT|I2HMLNy45k+uM8Zc^nw&AaZ~lR;iy8tjaex< z=)oP(3~9?_DieMU5-|7{AwPA~N<#R08f44x*u`AJ21lJ{AGublsC(r&ZPxCZ@G)jL zX}DUj-~MT3X*{et>@J9Sm$!5<^!UI@Bj~Ic0U5Nf~(ex z`xxquR!CK}E7KK7?c06E={U`BNwdYL{NBAp^`_~gT|-BMb|$_RBIHug3m2VilY90J#g3CO8oNfR zU9NbV?_i?8@(Z!|}mE{qhMZ%Aqfhu#K01+wFs|2|0-TV#Yo+QP2N^ z`@S1#xft6_0>Od_QQzuWgWb;-{5zmAY7FRnfr$WVGGdx_i;7|LqM8?M2w+Go%sC~i zadFt`0=TBQ2@sQto)k zewNd%Q410L%69Na-eLixZddltuE=q|?l-Vh*6isQk63@)tNCs|LSyS@7Xih}U>oSf zyn7_JUuPppt@CREwPi zD^|3@guXoVjU?~v>sPNZ4D?AmesvV@FGSsk8FiG42jYbIt)mWwZr&-!W3Qdr0scx) zpebf0dE!6O=MSvxIZ~lUsj`p_Q^(QlzO`s@qwDmH&Ab9g5qh15s~Q$|%jx#ckFT{W zArP7lF1zI?kZ)Kx3ZO>fCz!V2T*+R`tR77XOVk=c0)xDot#G0sj77BUW z+nn|Y;)ni(QyUy%|Ni+~sHhz(Uuts2{*=%t1{^lCk3xW+fiSw&>ffxb@%NHrbbq~f zbBADOZ-$$GUiUGXRG(OKF$XVh#{Pn5?8e+`^{rkxxj0Hjhb|N9-T1&{SkhG zJ!yB(L-sm$D2`y4#RS$h&<0ovZ+5Qx2BiTo2ftFv>Wes zj7X=(3(6XrAqi^zNb}pU=_f{;lirS(-8|0y3#w%|p5>_k<~(agcf4>_`0DAc_3f4Z z^+TdjueIYE74r;RuhmdnA6M@z1p?mo&% zi*4;D2^I6;6Cbp_0}-W5Yy{Q$pI@o0JuEYOXa@sU~6Jna1!N|r0eHCZ!&5ejOx z)?YMR-5|J#B5ufzUtODU5Lz1h%r zaU_|^MO~Un>K7+5MOXy2iMkDV%E+l-WZB;i+5bf`Kp^P#3j>q%sLe|q5s%T(Doom4 z)^7}eq#uOV;RX4Mf+*Ygvd27EwaBw0QoqRO*AE}k*1Djq! zGJHd>ll106TPmWyudj1Wgsa(erl>q-Rk?)B-mGLqRupmuVAHJQ#Dzykk6N$Y53~(@jTR|QnJEr#bx61$Zkh6J-UBzN^$9Vzu#m-Hq&nm!)qeSk)@z$|_NY6) zi-4^n`JtZO!_W2dOI6Bp>=*X3wjI@5fe*G{@L+~D4_&W)cYHlq>%<(FEyy7 zVxJyX$XUD@c>8W9ucihB$cVeLj4X2RmriY;1fIaU&LXdhFK}V<)dfAWW-y1QGfT~d zeGvc%(vj26GMM1!vc19y9>SCN)cbs~d9O>rxsYO+TTz@A``Ljp@qH@~Q04y0A}^!v z64zr`P{1PscNnO!nTbZ*v;f6GM^b^c(@T%U5&cprVjVSARk1}SS)-N?z)|&UxdEJ?x`6d|F3(qHp)uCmG#9F*+l*mFflCn zL#irR1wvsWOXQQP!L!DlV}aHvwEs*trl(oU%KRyzqRj5!8rSII3Kyd*-JlHGD2pM{ z*Q)5OqM{=B%90w3ftL25W+pJb0&xvc`ST`OkiwZ6@mEpS6EG6y8 ze9OX&bIZyGw`~=*7b&IF5n`C6*;k+X+78`6XT;t;kbOd&ULuFX>EE)jvnO}G5v>s0 z^9T&kk7*oBn*EYEY;*lKtz#PavG@4IrfTHPxoL3>S8`XWM^ ziV)XHGC^i^r%1Ycfug=(f{fsPMr->{#*Q%$19jTh%*{1)e|L+pw>$=-&2M0ERpB=g z%qT$t9ACE?w!P_{ua#s}by?QV-XEx_adyy)VOK{M+5I8eG)P+fCc!#5!I*G>{y+q$ zA<(c^gi6GNF+&+jfUuX$YN=wh@xZFqh&&I$Q;p5;F)epA6<@~nff>msMAioQ>bv;W zxz`?V)o(uPvMuY~e1zsoO!e9j1-W>AI((Us_I1Xxud6HJnHbsglP_EL+(xb1_5&g{ z&W)WYKCa8<^ruDlN|sz}$W_N1W7-oZH%yuOh+kjLYY{GHo1qiV!B!#-NePk znFulyVKEY-z8M3si$CeiN<}dEShm}ckW1s77tn}wc1ldD9?1@a9R~ShD+CJ65wNP* zv(QuGvs}mBw#r(}?WwM~!Y#)+%ZgQ|wuzu^Ukcxpt*P?Hs+EjTOke#(nXqN+uVc?~ zUk&}Q!mc~0$z_Y9SKtaFN-qK;Md=ENAOb1^5|t*sskDGpL+@Oaa4CYcAT<(#KRXC;=Dm4u-kJQ7nQyE zH3Yj2LkuKrhKgAs`I!%3L0@4Q48}TId}Oj-Qz!6ZECTU-fc5^+g|BhQj+@&P$!gG{ zT$cw$(@&CmloLuZA+nj2z&Anrt#XzR{~B*RloS%K%p;_zvxe(D(tTH(i$C$bMY&P% z>muSS@%X^BzZ>bxqBNQAc|VIPs4|HeS3d;9!S5fJ7RjZCP8FwkN&c!tMk9vHNV2PPk zNN>-M*Q&I>qAz8Yn_i85`XUp9!og}a%P;!!s2qJ>VTji6}HnE)jk*5S%exli)<50oR4{LtUo54 zVU3JOWvZv3h8A#5A>OW^^0N>90RuGA1tx)(V<)s@6%|wC$LUZ1gO-a8* zl`ECl#=RzPbXVRV`4LM2P zmz*q}s{hh3F*9JrPM+M4FdWGjhwBw@X(U8LVOm4k1?rhh5pww=Y(dTa$cj*8i<)a14UQzQ-;YGI#Zk#_<>n- zoeZPUslr)_K>-A^nxD?`ilDG=U|v+=6J6pQ6QA8$)!NbLIGMk?P9`W_ck0gDTb?W7 ztP+4Z2jJm|qx@WCf5ZfZ0q^5Ofm6Fhxxw|U=}Uj7RYQNCDNNAwkm3JDgg2*Vk-=E8 za(N)OMGdPwtXBW z#>o5Zq$;pD08`etaODE>vKjsM=4CIWg4pB86kgS$O8GJgi9e{4%C7ySIld#Yc*4!K zha-RH?gMkof!uamI|p|SbJJIq)Mz+Ye|#Z(x_PJmj3UKxnMo=t_yAwty};N6GK1j4 z1HuL%7exSsdH64#1N-=cKZkLVvLpAmmAvI__IefP-720Kkw>SGs|bINx`JC-00j8V zTWjIm2BG&ZnO3MEreYh0uM{Y_6F+3kUbH*XR()YJYheFNmi~d)4m|1iO<%&^9E zsX-C>Nn^2SwkV^>lwFpm%1`M$$JyX-7GtEr8#F?pbTlM94(DOoU;MEh?Wd)$@3HOf z$fy)$uXHI0c+h&S>-D-Hn6plcXV*0ve1J`L!Uxts4sdyc@3&=M5^g~iplQ!ohaM_& zL0`{g2UbVHm7)OMNGlmlda*%D)WxsGA<-43l&F$w$epC4c?^dz8P!U;iPorXWQh6B za@F}w04H<-SSrZkGZpWTvk>Qyhs8D;=4sH?6+8xQ*4e$Qsd>IF)4m$*WLQ9dQ&yIQ zhEhXy6_~?X0G9k%R~G?QL#Uis{j#yo-&L%8^A7)7@8+Nq@F@@!6*R1LN~?Ld49>`7 z9_NxwsvEXymVDK@Y#iB6vMh~2V+z9EB0!)xja^~Gi_~cfpxjlznWlsGXBz!?qr-)*9Ozy zgvQ&6P6FuBO|ClHO+}o`$vUz7z@jbn)xFetP}$D(y=%40Sj)y&>{f}M4!cW@ohJ2C zKyE^Wpl}IMt z2FNGgt0QYc_&!JmOacxZ4NnVeYxgLhY5*5v3JukJceFF&O}zjWE3nN;I8Q&!Gd^qU z=y;I8qXPaS5m1yc+aRP;X!v7hakd2}VC=y5RGLft2AKS@6Q&+xK_L5hB$t9|QlGm# zaw$_`H+v{4rM?pSdnJMavh2&CW=}NyhTp2AYIEmHpfBH9;{aTcs37XBhb#6?GC^P5 z_8Zi{`K;P2_2pA!g5yYcs$=gWWorC|h(Lcs!fXH9-Es^Sls1pxX&ewOoUpzq1FLSa z7Zr`1)Egz#A~HcXL8NO+XZIIRFTWU`_Mc7w!bwd% zy?7tmRX`wOBMq={F@I8*@a47x-ox#UCFlgmk80=h#@MNW95AwWqG?E|Fi}BaVRTCF zqfFG$uLBp%gK&KC0~j~wf^)j&0&Q**h{|AKWGpg6zkd%Gw;2J`BuD(DC?}T0{R-GD z4umzN#f%{h+!v`xrMaX0LZg)U@{$*9X&tM-`g01l2{FBU>ri)JTT-A3=kDtqIr}>6 z&`Vmls|pOuNUBOZS}D$r-O@)~ab6h}`(e_+u7R9%~RXQ4mfm1R?3BTnv-7dD}5 zz=oR0alFL_I7>=OK1{tOLpq`9y*{6vmq&Bzq*&cfF@pRw;L@xHY^s2h%635Y!=k<4 zkH09lT#R76bcF5Q=d_cmAjdDEohQ;5kTyOvhYk3hDIRyNm%_*ZRYV|5ESZY-d3 z5Pq6TQF8xGWf5>Q1N_5JKpN*KyK{O%LPBd^HBv7pfX>4&eBlq6xSRF~xxX_5cnlW5 zV)eS_sK12Y^4)S_yTNh>_@xOW(^TEmSEk~haQ=CUjh)@V4>dk+rVGc+{rvefVI=5S zx%?0=8?Xz)7ZNs>_7`40n0`{dm9G;izv5&QqL|br6g%v(pVc!2aa8xyoy;W;KElJs zCk6D3C^IcYi0LWm<)uO=^Cl?qZgN{Om-n_ldPnJt8CmF@jP*&5i$*X)&vv-m@N z$_$xbW0z}@xli^7(_=C7BrZG8m@*r08FoH;u6GRD!m*|C^Rj+lk{|GjF8B@~EkhoC z8_zhfJ2RCr;p{V1IdSt4fR~X)GNh{xm2Kb-$+T8ChXFAHi)uo z_Svdea49tr#V8pXc9J+7;zf_7OUInYUs<&dW|PU)8FIIe;ns9L{%6qXE#@y)4wXYl z*)0aCiA(lJojibFf8%h!CsJ9=U~xBM>P$v1)!NLvS_k0NcvzUF#%%2GH$kE~GMHDG zetx=ld&V&i54;18=U-VYvMnZ#Lg(KeUcA|r!*Hti@cpZTgityvihC()45zsF?q}8V zD%^na`TQMph1jC`}G@NNnE PpcSf0YWMQ*J`MaAgSm76 literal 36545 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 Date: Tue, 21 Feb 2023 17:51:05 +0100 Subject: [PATCH 254/281] update plugin menu --- website/docs/artist_hosts_tvpaint.md | 34 ++++++++++------------ website/docs/assets/tvp_openpype_menu.png | Bin 3565 -> 6704 bytes 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/website/docs/artist_hosts_tvpaint.md b/website/docs/artist_hosts_tvpaint.md index baa8c0a09d..3e1fd6339b 100644 --- a/website/docs/artist_hosts_tvpaint.md +++ b/website/docs/artist_hosts_tvpaint.md @@ -6,37 +6,35 @@ sidebar_label: TVPaint - [Work Files](artist_tools_workfiles) - [Load](artist_tools_loader) -- [Create](artist_tools_creator) -- [Subset Manager](artist_tools_subset_manager) - [Scene Inventory](artist_tools_inventory) - [Publish](artist_tools_publisher) - [Library](artist_tools_library) ## Setup -When you launch TVPaint with OpenPype for the very first time it is necessary to do some additional steps. Right after the TVPaint launching a few system windows will pop up. +When you launch TVPaint with OpenPype for the very first time it is necessary to do some additional steps. Right after the TVPaint launching a few system windows will pop up. ![permission](assets/tvp_permission.png) -Choose `Replace the file in the destination`. Then another window shows up. +Choose `Replace the file in the destination`. Then another window shows up. ![permission2](assets/tvp_permission2.png) Click on `Continue`. -After opening TVPaint go to the menu bar: `Windows → Plugins → OpenPype`. +After opening TVPaint go to the menu bar: `Windows → Plugins → OpenPype`. ![pypewindow](assets/tvp_hidden_window.gif) -Another TVPaint window pop up. Please press `Yes`. This window will be presented in every single TVPaint launching. Unfortunately, there is no other way how to workaround it. +Another TVPaint window pop up. Please press `Yes`. This window will be presented in every single TVPaint launching. Unfortunately, there is no other way how to workaround it. ![writefile](assets/tvp_write_file.png) -Now OpenPype Tools menu is in your TVPaint work area. +Now OpenPype Tools menu is in your TVPaint work area. ![openpypetools](assets/tvp_openpype_menu.png) -You can start your work. +You can start your work. --- @@ -67,7 +65,7 @@ TVPaint integration tries to not guess what you want to publish from the scene.
-Render Layer bakes all the animation layers of one particular color group together. +Render Layer bakes all the animation layers of one particular color group together. - In the **Create** tab, pick `Render Layer` - Fill `variant`, type in the name that the final published RenderLayer should have according to the naming convention in your studio. *(L10, BG, Hero, etc.)* @@ -95,11 +93,11 @@ In the bottom left corner of your timeline, you will note a **Color group** butt ![colorgroups](assets/tvp_color_groups.png) -It allows you to choose a group by checking one of the colors of the color list. +It allows you to choose a group by checking one of the colors of the color list. ![colorgroups](assets/tvp_color_groups2.png) -The timeline's animation layer can be marked by the color you pick from your Color group. Layers in the timeline with the same color are gathered into a group represents one render layer. +The timeline's animation layer can be marked by the color you pick from your Color group. Layers in the timeline with the same color are gathered into a group represents one render layer. ![timeline](assets/tvp_timeline_color.png) @@ -135,25 +133,25 @@ You can change `variant` or Render Layer later in **Publish** tab.
:::warning -You cannot change TVPaint layer name once you mark it as part of Render Pass. You would have to remove created Render Pass and create it again with new TVPaint layer name. +You cannot change TVPaint layer name once you mark it as part of Render Pass. You would have to remove created Render Pass and create it again with new TVPaint layer name. :::

-In this example, OpenPype will render selected animation layers within the given color group. E.i. the layers *L020_colour_fx*, *L020_colour_mouth*, and *L020_colour_eye* will be rendered as one pass belonging to the yellow RenderLayer. +In this example, OpenPype will render selected animation layers within the given color group. E.i. the layers *L020_colour_fx*, *L020_colour_mouth*, and *L020_colour_eye* will be rendered as one pass belonging to the yellow RenderLayer. ![renderpass](assets/tvp_timeline_color2.png) -Now that you have created the required instances, you can publish them. +Now that you have created the required instances, you can publish them. - Fill the comment on the bottom of the window -- Double check enabled instance and their context +- Double check enabled instance and their context - Press `Publish` - Wait to finish -- Once the `Publisher` turns gets green your renders have been published. +- Once the `Publisher` turns gets green your renders have been published. --- -## Load +## Load When you want to load existing published work you can reach the `Loader` through the OpenPype Tools `Load` button. The supported families for TVPaint are: @@ -174,4 +172,4 @@ Scene Inventory shows you everything that you have loaded into your scene using ![sceneinventory](assets/tvp_scene_inventory.png) -You can switch to a previous version of the file or update it to the latest or delete items. +You can switch to a previous version of the file or update it to the latest or delete items. diff --git a/website/docs/assets/tvp_openpype_menu.png b/website/docs/assets/tvp_openpype_menu.png index cb5c2d4aac02c9187d38420947c3dad25cc1eaa6..23eaf33fc3d2ba30452454eb2ffb4abf1f5337c8 100644 GIT binary patch literal 6704 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 literal 3565 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ΜdRthcI%Jgf}%x5NHEtv;k^Aby4zi}pSE zX^T;r#Y2J95#iRF!a|{Gi<^5X`mWgR4~Xv9V(e<|#ci0WrFz5hL4W@qES1gxO8zF{ zR!Zdct|L(e|BwqC+{%UXbKUhOO{)_hHsY$PE3VnF+G#UIXUO;Spkz{rRLBEghCl$130pcakJZ>XE!$F)^q{0R9-;U2E#vhNCUdul1XsSl7G- zZ^`XZa{qeD!CRPiYn;Auh{bjs&S9Hd$y8akoNClwn^2ghvzl5vl#S-o ze2-v>;jA$x^wjdBfY5A4HT@J5dDKKd7o9=0#o>rD?jLg}t`#Q_uU@#Ppm!7h5n{9T zVw%OzZ;vRmw<~RQGFm%3XF8g=`&T+Fr(FCsapB@vhEG7*YqG6I#@5m8t#ifpmPtaBVF5&;DghigBLQ{#sk#MC+Vq84C6?)Ws>|KD5=cpqH z%x87A#w0NuoFhL+6hb~#89p6AyEGwok8)&A(Gn9RC6n@?fL^^GpcISOG3%%`fx5%B zdBJknoTdz+xN-+x6)+&|x#+jt1ObkEC#5BUA6o zm>xS9lu-#4(K3UL{gR5pT za^^bfj_7GcrQ)lf5va|t@~s*!^+a#nO=C=|-p5wURu0UhLxi(GCJ+Hj3(l@%HPsK~ y997S?#{lekeon!^F!dMW+Tem`8+CxYLZ^#+FOju}V_3gv08=9~!%~AQ(f Date: Tue, 21 Feb 2023 18:29:37 +0100 Subject: [PATCH 255/281] add missing image --- website/docs/assets/tvp_publisher.png | Bin 0 -> 200677 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/docs/assets/tvp_publisher.png diff --git a/website/docs/assets/tvp_publisher.png b/website/docs/assets/tvp_publisher.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b1f936dfec1ec4f35fce41a419e53a72c6468e GIT binary patch literal 200677 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

;r>#l_M$0;nH4viktX?A?B}GQ;WQaE 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�d92;(`4A*w~J*L5*xpBb*ZM1C~{kGgc3F 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 literal 0 HcmV?d00001 From c3d02ca051cbbec4bb9d81f5d68bacb66ee8fbf9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 21 Feb 2023 18:32:09 +0100 Subject: [PATCH 256/281] Apply suggestions from code review Co-authored-by: Toke Jepsen --- website/docs/artist_hosts_tvpaint.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/website/docs/artist_hosts_tvpaint.md b/website/docs/artist_hosts_tvpaint.md index 3e1fd6339b..a8a6cee5f8 100644 --- a/website/docs/artist_hosts_tvpaint.md +++ b/website/docs/artist_hosts_tvpaint.md @@ -42,7 +42,7 @@ You can start your work. In TVPaint you can find the Tools in OpenPype menu extension. The OpenPype Tools menu should be available in your work area. However, sometimes it happens that the Tools menu is hidden. You can display the extension panel by going to `Windows -> Plugins -> OpenPype`. ## Create & Publish -As you might already know, to be able to publish, you have to mark what should be published. The marking part is called **Create**. In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Layers](#render-layer)** and **[Render Passes](#render-pass)**. +To be able to publish, you have to mark what should be published. The marking part is called **Create**. In TVPaint you can create and publish **[Reviews](#review)**, **[Workfile](#workfile)**, **[Render Layers](#render-layer)** and **[Render Passes](#render-pass)**. :::important TVPaint integration tries to not guess what you want to publish from the scene. Therefore, you should tell what you want to publish. @@ -74,7 +74,7 @@ Render Layer bakes all the animation layers of one particular color group togeth - or select a layer of a particular color and set combobox to **<Use selection>** - Hit `Create` button -You have just created Render Layer. Now choose any amount of animation layers that need to be rendered together and assign them the color group. +After creating a RenderLayer, choose any amount of animation layers that need to be rendered together and assign them the color group. You can change `variant` later in **Publish** tab. @@ -107,20 +107,20 @@ The timeline's animation layer can be marked by the color you pick from your Col Render Passes are smaller individual elements of a [Render Layer](artist_hosts_tvpaint.md#render-layer). A `character` render layer might consist of multiple render passes such as `Line`, `Color` and `Shadow`. -Render Passes are specific because they have to belong to a particular Render Layer. You have to select to which Render Layer the pass belongs. Try to refresh if you don't see demanded Render Layer in the options. +Render Passes are specific because they have to belong to a particular Render Layer. You have to select to which Render Layer the pass belongs. Try to refresh if you don't see a specific Render Layer in the options.

When you want to create Render Pass -- choose one or several TVPaint layers -- In the **Create** tab, pick `Render Pass` -- Fill the `variant` with desired name of pass, e.g. `Color`. -- Select Render Layer to which belongs in Render Layer combobox - - If you don't see new Render Layer try refresh first +- choose one or several TVPaint layers. +- in the **Create** tab, pick `Render Pass`. +- fill the `variant` with desired name of pass, e.g. `Color`. +- select the Render Layer you want the Render Pass to belong to from the combobox. + - if you don't see new Render Layer try refresh first. - Press `Create` -You have just created Render Pass. Selected TVPaint layers should be marked with color group of Render Layer. +After creating a Render Pass, selected the TVPaint layers that should be marked with color group of Render Layer. You can change `variant` or Render Layer later in **Publish** tab. @@ -143,11 +143,11 @@ In this example, OpenPype will render selected animation layers within the given ![renderpass](assets/tvp_timeline_color2.png) Now that you have created the required instances, you can publish them. -- Fill the comment on the bottom of the window -- Double check enabled instance and their context -- Press `Publish` -- Wait to finish -- Once the `Publisher` turns gets green your renders have been published. +- Fill the comment on the bottom of the window. +- Double check enabled instance and their context. +- Press `Publish`. +- Wait to finish. +- Once the `Publisher` turns turns green your renders have been published. --- From d08c979a2145ea46d66c52408913081d3f4486e6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 08:07:38 +0000 Subject: [PATCH 257/281] Fix setting scene fps with float input --- openpype/hosts/maya/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 84c6e6929d..509168278c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1997,7 +1997,7 @@ def set_scene_fps(fps, update=True): '48000': '48000fps' } - unit = fps_mapping.get(str(fps), None) + unit = fps_mapping.get(str(convert_to_maya_fps(fps)), None) if unit is None: raise ValueError("Unsupported FPS value: `%s`" % fps) @@ -3454,11 +3454,11 @@ def convert_to_maya_fps(fps): # If input fps is a whole number we'll return. if float(fps).is_integer(): # Validate fps is part of Maya's fps selection. - if fps not in int_framerates: + if int(fps) not in int_framerates: raise ValueError( "Framerate \"{}\" is not supported in Maya".format(fps) ) - return fps + return int(fps) else: # Differences to supported float frame rates. differences = [] From 17c666d2f398406afacc6e8b5b1d8c12039e5bed Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 11:47:47 +0000 Subject: [PATCH 258/281] Code cosmetics. --- openpype/hosts/maya/plugins/publish/validate_maya_units.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index ad256b6a72..357dde692c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -4,7 +4,6 @@ import pyblish.api import openpype.hosts.maya.api.lib as mayalib from openpype.pipeline.context_tools import get_current_project_asset -from math import ceil from openpype.pipeline.publish import ( RepairContextAction, ValidateSceneOrder, From 540a4977014914f4c453031522519d16d7342f34 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 22 Feb 2023 03:28:50 +0000 Subject: [PATCH 259/281] [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 72d6b64c60..bb5171764c 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1" +__version__ = "3.15.2-nightly.1" From 03d01430e60f5d4ec37c1d5e1e8df2868b7f6b1e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 22 Feb 2023 10:27:31 +0100 Subject: [PATCH 260/281] :rotating_light: few style fixes --- openpype/hosts/max/api/lib.py | 8 ++++---- openpype/hosts/max/api/lib_rendersettings.py | 2 +- openpype/hosts/max/plugins/publish/collect_render.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 3383330792..4fb750d91b 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -133,7 +133,7 @@ def get_default_render_folder(project_setting=None): ["default_render_image_folder"]) -def set_framerange(startFrame, endFrame): +def set_framerange(start_frame, end_frame): """ Note: Frame range can be specified in different types. Possible values are: @@ -146,9 +146,9 @@ def set_framerange(startFrame, endFrame): Current type is hard-coded, there should be a custom setting for this. """ rt.rendTimeType = 4 - if startFrame is not None and endFrame is not None: - frameRange = "{0}-{1}".format(startFrame, endFrame) - rt.rendPickupFrames = frameRange + if start_frame is not None and end_frame is not None: + frame_range = "{0}-{1}".format(start_frame, end_frame) + rt.rendPickupFrames = frame_range def get_multipass_setting(project_setting=None): diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index b07d19f176..4940265a23 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -62,7 +62,7 @@ class RenderSettings(object): # hard-coded, should be customized in the setting context = get_current_project_asset() - # get project reoslution + # get project resolution width = context["data"].get("resolutionWidth") height = context["data"].get("resolutionHeight") # Set Frame Range diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 7656c641ed..7c9e311c2f 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -31,7 +31,7 @@ class CollectRender(pyblish.api.InstancePlugin): render_layer_files = RenderProducts().render_product(instance.name) folder = folder.replace("\\", "/") - imgFormat = RenderProducts().image_format() + img_format = RenderProducts().image_format() project_name = context.data["projectName"] asset_doc = context.data["assetEntity"] asset_id = asset_doc["_id"] @@ -53,7 +53,7 @@ class CollectRender(pyblish.api.InstancePlugin): "asset": asset, "publish": True, "maxversion": str(get_max_version()), - "imageFormat": imgFormat, + "imageFormat": img_format, "family": 'maxrender', "families": ['maxrender'], "source": filepath, From 3b432690ace63aa1d69baff80de3ff3d86c9c8c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:00:20 +0100 Subject: [PATCH 261/281] removed settings for plugin 'CollectRenderScene' The plugin is not available anymore --- .../defaults/project_settings/tvpaint.json | 4 ---- .../schema_project_tvpaint.json | 24 ------------------- 2 files changed, 28 deletions(-) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 74a5af403c..340181b3a4 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -43,10 +43,6 @@ } }, "publish": { - "CollectRenderScene": { - "enabled": false, - "render_layer": "Main" - }, "ExtractSequence": { "review_bg": [ 255, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index d09c666d50..55e60357e5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -204,30 +204,6 @@ "key": "publish", "label": "Publish plugins", "children": [ - { - "type": "dict", - "collapsible": true, - "key": "CollectRenderScene", - "label": "Collect Render Scene", - "is_group": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "label", - "label": "It is possible to fill 'render_layer' or 'variant' in subset name template with custom value.
- value of 'render_pass' is always \"beauty\"." - }, - { - "type": "text", - "key": "render_layer", - "label": "Render Layer" - } - ] - }, { "type": "dict", "collapsible": true, From 7692ff252f21af427062e1edef1852589e904d6f Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 31 Jan 2023 16:56:01 +0100 Subject: [PATCH 262/281] use .get() to not throw error handle_start/end is optional and keys isn't created on Kitsu sync if data doesn't exists on Kitsus side --- openpype/hosts/fusion/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index a33e5cf289..088f597208 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -68,8 +68,8 @@ def set_asset_framerange(): asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] - handle_start = asset_doc["data"]["handleStart"] - handle_end = asset_doc["data"]["handleEnd"] + handle_start = asset_doc["data"].get("handleStart") + handle_end = asset_doc["data"].get("handleEnd") update_frame_range(start, end, set_render_range=True, handle_start=handle_start, handle_end=handle_end) From 9eadff95ed3dcfcb214b22c1e77a3ec15d4fa82f Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Feb 2023 19:41:00 +0100 Subject: [PATCH 263/281] Generate the file list using metadata instead of files in render folder This solves the problem with the preview file generated from a previous render is still in the render folder --- .../fusion/plugins/publish/render_local.py | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index 79e458b40a..d1d32fbf91 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -1,5 +1,5 @@ import os -from pprint import pformat +import copy import pyblish.api from openpype.hosts.fusion.api import comp_lock_and_undo_chunk @@ -19,10 +19,43 @@ class Fusionlocal(pyblish.api.InstancePlugin): families = ["render.local"] def process(self, instance): - + # This plug-in runs only once and thus assumes all instances # currently will render the same frame range context = instance.context + self.render_once(context) + + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + basename = os.path.basename(path) + head, ext = os.path.splitext(basename) + files = [ + f"{head}{frame}{ext}" for frame in range(frame_start, frame_end+1) + ] + repre = { + 'name': ext[1:], + 'ext': ext[1:], + 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, + 'files': files, + "stagingDir": output_dir, + } + + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(repre) + + # review representation + repre_preview = repre.copy() + repre_preview["name"] = repre_preview["ext"] = "mp4" + repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"] + instance.data["representations"].append(repre_preview) + + def render_once(self, context): + """Render context comp only once, even with more render instances""" + key = "__hasRun{}".format(self.__class__.__name__) if context.data.get(key, False): return @@ -32,10 +65,6 @@ class Fusionlocal(pyblish.api.InstancePlugin): current_comp = context.data["currentComp"] frame_start = context.data["frameStartHandle"] frame_end = context.data["frameEndHandle"] - path = instance.data["path"] - output_dir = instance.data["outputDir"] - - ext = os.path.splitext(os.path.basename(path))[-1] self.log.info("Starting render") self.log.info("Start frame: {}".format(frame_start)) @@ -48,26 +77,5 @@ class Fusionlocal(pyblish.api.InstancePlugin): "Wait": True }) - if "representations" not in instance.data: - instance.data["representations"] = [] - - collected_frames = os.listdir(output_dir) - repre = { - 'name': ext[1:], - 'ext': ext[1:], - 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, - 'files': collected_frames, - "stagingDir": output_dir, - } - instance.data["representations"].append(repre) - - # review representation - repre_preview = repre.copy() - repre_preview["name"] = repre_preview["ext"] = "mp4" - repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"] - instance.data["representations"].append(repre_preview) - - self.log.debug(f"_ instance.data: {pformat(instance.data)}") - if not result: raise RuntimeError("Comp render failed") From 723778c3ad92b4a2609996b4c0d6d357335c3830 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Feb 2023 11:46:03 +0100 Subject: [PATCH 264/281] zfill 4 for frame value Fusion defaults with 4 numbers for the frame number so if shot doesn't start at 1000 we should zfill to match. --- openpype/hosts/fusion/plugins/publish/render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index d1d32fbf91..5a0b068e8c 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -33,7 +33,7 @@ class Fusionlocal(pyblish.api.InstancePlugin): basename = os.path.basename(path) head, ext = os.path.splitext(basename) files = [ - f"{head}{frame}{ext}" for frame in range(frame_start, frame_end+1) + f"{head}{str(frame).zfill(4)}{ext}" for frame in range(frame_start, frame_end+1) ] repre = { 'name': ext[1:], From 4ad830ffdb9b48ff3455f8c11fd7a681d603c7cf Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 00:21:37 +0100 Subject: [PATCH 265/281] Fixed hound-bots comments --- .../fusion/plugins/publish/render_local.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index 5a0b068e8c..b02fa55d20 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -1,6 +1,4 @@ import os -import copy - import pyblish.api from openpype.hosts.fusion.api import comp_lock_and_undo_chunk @@ -19,7 +17,7 @@ class Fusionlocal(pyblish.api.InstancePlugin): families = ["render.local"] def process(self, instance): - + # This plug-in runs only once and thus assumes all instances # currently will render the same frame range context = instance.context @@ -33,12 +31,12 @@ class Fusionlocal(pyblish.api.InstancePlugin): basename = os.path.basename(path) head, ext = os.path.splitext(basename) files = [ - f"{head}{str(frame).zfill(4)}{ext}" for frame in range(frame_start, frame_end+1) + f"{head}{str(frame).zfill(4)}{ext}" for frame in range(frame_start, frame_end + 1) ] repre = { 'name': ext[1:], 'ext': ext[1:], - 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, + 'frameStart': f"%0{len(str(frame_end))}d" % frame_start, 'files': files, "stagingDir": output_dir, } @@ -56,19 +54,19 @@ class Fusionlocal(pyblish.api.InstancePlugin): def render_once(self, context): """Render context comp only once, even with more render instances""" - key = "__hasRun{}".format(self.__class__.__name__) + key = f'__hasRun{self.__class__.__name__}' if context.data.get(key, False): return - else: - context.data[key] = True + + context.data[key] = True current_comp = context.data["currentComp"] frame_start = context.data["frameStartHandle"] frame_end = context.data["frameEndHandle"] self.log.info("Starting render") - self.log.info("Start frame: {}".format(frame_start)) - self.log.info("End frame: {}".format(frame_end)) + self.log.info(f"Start frame: {frame_start}") + self.log.info(f"End frame: {frame_end}") with comp_lock_and_undo_chunk(current_comp): result = current_comp.Render({ From f7175511f044745f88acb9c927a6f7ea51da705f Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 12:11:04 +0100 Subject: [PATCH 266/281] Move hasRun check back to process function --- .../hosts/fusion/plugins/publish/render_local.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index b02fa55d20..b49c11e366 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -21,6 +21,12 @@ class Fusionlocal(pyblish.api.InstancePlugin): # This plug-in runs only once and thus assumes all instances # currently will render the same frame range context = instance.context + key = f"__hasRun{self.__class__.__name__}" + if context.data.get(key, False): + return + + context.data[key] = True + self.render_once(context) frame_start = context.data["frameStartHandle"] @@ -54,12 +60,6 @@ class Fusionlocal(pyblish.api.InstancePlugin): def render_once(self, context): """Render context comp only once, even with more render instances""" - key = f'__hasRun{self.__class__.__name__}' - if context.data.get(key, False): - return - - context.data[key] = True - current_comp = context.data["currentComp"] frame_start = context.data["frameStartHandle"] frame_end = context.data["frameEndHandle"] From fa526b3e4b2fe40a24dc7f34492c5864b818d9a3 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 12:14:45 +0100 Subject: [PATCH 267/281] Made line 40 shorter --- openpype/hosts/fusion/plugins/publish/render_local.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index b49c11e366..6f8cd66bd6 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -37,7 +37,8 @@ class Fusionlocal(pyblish.api.InstancePlugin): basename = os.path.basename(path) head, ext = os.path.splitext(basename) files = [ - f"{head}{str(frame).zfill(4)}{ext}" for frame in range(frame_start, frame_end + 1) + f"{head}{str(frame).zfill(4)}{ext}" + for frame in range(frame_start, frame_end + 1) ] repre = { 'name': ext[1:], From eb3dd357e3a5406bae2f6ddb856412d44a78a815 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 14:11:15 +0100 Subject: [PATCH 268/281] Changed LaunchFailed message from PYTHON36 to PYTHON PATH --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index d043d54322..323b8b0029 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -36,7 +36,7 @@ class FusionPrelaunch(PreLaunchHook): "Make sure the environment in fusion settings has " "'FUSION_PYTHON3_HOME' set correctly and make sure " "Python 3 is installed in the given path." - f"\n\nPYTHON36: {fusion_python3_home}" + f"\n\nPYTHON PATH: {fusion_python3_home}" ) self.log.info(f"Setting {py3_var}: '{py3_dir}'...") From 6482481cf55089420b479f16ab4fa579ec76676e Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 14:13:59 +0100 Subject: [PATCH 269/281] handleStart/End is now required --- openpype/hosts/fusion/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 088f597208..a33e5cf289 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -68,8 +68,8 @@ def set_asset_framerange(): asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] - handle_start = asset_doc["data"].get("handleStart") - handle_end = asset_doc["data"].get("handleEnd") + handle_start = asset_doc["data"]["handleStart"] + handle_end = asset_doc["data"]["handleEnd"] update_frame_range(start, end, set_render_range=True, handle_start=handle_start, handle_end=handle_end) From a9e1140ec9a5c4c0f347e4a8e5afa7da4a89f781 Mon Sep 17 00:00:00 2001 From: Ember Light <49758407+EmberLightVFX@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:14:30 +0100 Subject: [PATCH 270/281] Update openpype/hosts/fusion/plugins/publish/render_local.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- openpype/hosts/fusion/plugins/publish/render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index 6f8cd66bd6..9bd867a55a 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -55,7 +55,7 @@ class Fusionlocal(pyblish.api.InstancePlugin): # review representation repre_preview = repre.copy() repre_preview["name"] = repre_preview["ext"] = "mp4" - repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"] + repre_preview["tags"] = ["review", "ftrackreview", "delete"] instance.data["representations"].append(repre_preview) def render_once(self, context): From aa1dd161a5c9add531d53733b77b8cbc5f651e48 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 14:17:59 +0100 Subject: [PATCH 271/281] Removed double space after "," in repre_preview["tags"] --- openpype/hosts/fusion/plugins/publish/render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index 9bd867a55a..53d8eb64e1 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -55,7 +55,7 @@ class Fusionlocal(pyblish.api.InstancePlugin): # review representation repre_preview = repre.copy() repre_preview["name"] = repre_preview["ext"] = "mp4" - repre_preview["tags"] = ["review", "ftrackreview", "delete"] + repre_preview["tags"] = ["review", "ftrackreview", "delete"] instance.data["representations"].append(repre_preview) def render_once(self, context): From 0124bba40cd55c30517e9a89fc89405dc86c6081 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 13:24:50 +0100 Subject: [PATCH 272/281] get_representation_parents() missed project_name --- openpype/hosts/fusion/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index a33e5cf289..ac04efa6a0 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -210,7 +210,7 @@ def switch_item(container, if any(not x for x in [asset_name, subset_name, representation_name]): repre_id = container["representation"] representation = get_representation_by_id(project_name, repre_id) - repre_parent_docs = get_representation_parents(representation) + repre_parent_docs = get_representation_parents(project_name, representation) if repre_parent_docs: version, subset, asset, _ = repre_parent_docs else: From 7e4457b241347b39fb1606751ba723e1be774632 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 13:25:57 +0100 Subject: [PATCH 273/281] Fixed hound's max-length note --- openpype/hosts/fusion/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index ac04efa6a0..88a3f0b49b 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -210,7 +210,8 @@ def switch_item(container, if any(not x for x in [asset_name, subset_name, representation_name]): repre_id = container["representation"] representation = get_representation_by_id(project_name, repre_id) - repre_parent_docs = get_representation_parents(project_name, representation) + repre_parent_docs = get_representation_parents( + project_name, representation) if repre_parent_docs: version, subset, asset, _ = repre_parent_docs else: From 44672f3f827517575119d541a1d3c76a3c7fcc2b Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 17:29:18 +0100 Subject: [PATCH 274/281] add task to instance --- openpype/hosts/fusion/plugins/publish/collect_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index fe60b83827..7b0a1b6369 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -80,6 +80,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, + "task": context.data["task"], "frameStart": context.data["frameStart"], "frameEnd": context.data["frameEnd"], "frameStartHandle": context.data["frameStartHandle"], From c26f86f08c8517c081e35292973b0713f2346e15 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:12:19 +0100 Subject: [PATCH 275/281] Nuke: adding solution for originalBasename frame temlate formating https://github.com/ynput/OpenPype/pull/4452#issuecomment-1426020567 --- openpype/hosts/nuke/plugins/load/load_clip.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 565d777811..f9364172ea 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -220,8 +220,21 @@ class LoadClip(plugin.NukeLoader): dict: altered representation data """ representation = deepcopy(representation) - frame = representation["context"]["frame"] - representation["context"]["frame"] = "#" * len(str(frame)) + context = representation["context"] + template = representation["data"]["template"] + frame = context["frame"] + hashed_frame = "#" * len(str(frame)) + + if ( + "{originalBasename}" in template + and "frame" in context + ): + origin_basename = context["originalBasename"] + context["originalBasename"] = origin_basename.replace( + frame, hashed_frame + ) + + representation["context"]["frame"] = hashed_frame return representation def update(self, container, representation): From ebb477e068b19289963aab53a99a2da503d5f52b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:51:29 +0100 Subject: [PATCH 276/281] publishing files with fixed versionData to fit originalBasename tempate --- .../publish/collect_otio_subset_resources.py | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index e72c12d9a9..537aef683c 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -14,16 +14,41 @@ from openpype.pipeline.editorial import ( range_from_frames, make_sequence_collection ) - +from openpype.pipeline.publish import ( + get_publish_template_name +) class CollectOtioSubsetResources(pyblish.api.InstancePlugin): """Get Resources for a subset version""" label = "Collect OTIO Subset Resources" - order = pyblish.api.CollectorOrder - 0.077 + order = pyblish.api.CollectorOrder + 0.0021 families = ["clip"] hosts = ["resolve", "hiero", "flame"] + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Anatomy data is pre-filled by Collectors + context = instance.context + project_name = context.data["projectName"] + + # Task can be optional in anatomy data + host_name = context.data["hostName"] + family = instance.data["family"] + anatomy_data = instance.context.data["anatomyData"] + task_info = anatomy_data.get("task") or {} + + return get_publish_template_name( + project_name, + host_name, + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=context.data["project_settings"], + logger=self.log + ) + def process(self, instance): if "audio" in instance.data["family"]: @@ -35,6 +60,13 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): if not instance.data.get("versionData"): instance.data["versionData"] = {} + template_name = self.get_template_name(instance) + anatomy = instance.context.data["anatomy"] + publish_template_category = anatomy.templates[template_name] + template = os.path.normpath(publish_template_category["path"]) + self.log.debug( + ">> template: {}".format(template)) + handle_start = instance.data["handleStart"] handle_end = instance.data["handleEnd"] @@ -84,6 +116,10 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_start = instance.data["frameStart"] frame_end = frame_start + (media_out - media_in) + if "{originalDirname}" in template: + frame_start = media_in + frame_end = media_out + # add to version data start and end range data # for loader plugins to be correctly displayed and loaded instance.data["versionData"].update({ @@ -153,7 +189,6 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): repre = self._create_representation( frame_start, frame_end, collection=collection) - instance.data["originalBasename"] = collection.format("{head}") else: _trim = False dirname, filename = os.path.split(media_ref.target_url) @@ -168,8 +203,6 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): repre = self._create_representation( frame_start, frame_end, file=filename, trim=_trim) - instance.data["originalBasename"] = os.path.splitext(filename)[0] - instance.data["originalDirname"] = self.staging_dir if repre: From 8eae684f01da394e18407692d89062b3385a3bd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:53:36 +0100 Subject: [PATCH 277/281] polishing fixes --- .../publish/collect_otio_subset_resources.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 537aef683c..5daa1b40fe 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -26,28 +26,6 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): families = ["clip"] hosts = ["resolve", "hiero", "flame"] - def get_template_name(self, instance): - """Return anatomy template name to use for integration""" - - # Anatomy data is pre-filled by Collectors - context = instance.context - project_name = context.data["projectName"] - - # Task can be optional in anatomy data - host_name = context.data["hostName"] - family = instance.data["family"] - anatomy_data = instance.context.data["anatomyData"] - task_info = anatomy_data.get("task") or {} - - return get_publish_template_name( - project_name, - host_name, - family, - task_name=task_info.get("name"), - task_type=task_info.get("type"), - project_settings=context.data["project_settings"], - logger=self.log - ) def process(self, instance): @@ -116,6 +94,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_start = instance.data["frameStart"] frame_end = frame_start + (media_out - media_in) + # Fit start /end frame to media in /out if "{originalDirname}" in template: frame_start = media_in frame_end = media_out @@ -258,3 +237,26 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): if kwargs.get("trim") is True: representation_data["tags"] = ["trim"] return representation_data + + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Anatomy data is pre-filled by Collectors + context = instance.context + project_name = context.data["projectName"] + + # Task can be optional in anatomy data + host_name = context.data["hostName"] + family = instance.data["family"] + anatomy_data = instance.context.data["anatomyData"] + task_info = anatomy_data.get("task") or {} + + return get_publish_template_name( + project_name, + host_name, + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=context.data["project_settings"], + logger=self.log + ) \ No newline at end of file From c103ac19076736ded0755c54737c2f9af2b408ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:54:54 +0100 Subject: [PATCH 278/281] wrong template key name --- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 5daa1b40fe..dab52986da 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -95,7 +95,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_end = frame_start + (media_out - media_in) # Fit start /end frame to media in /out - if "{originalDirname}" in template: + if "{originalBasename}" in template: frame_start = media_in frame_end = media_out From 80ac19d4c9af3e24f72738f820b64b546fc9b764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 14 Feb 2023 12:18:00 +0100 Subject: [PATCH 279/281] Update openpype/hosts/nuke/plugins/load/load_clip.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/nuke/plugins/load/load_clip.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index f9364172ea..8f9b463037 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -222,13 +222,12 @@ class LoadClip(plugin.NukeLoader): representation = deepcopy(representation) context = representation["context"] template = representation["data"]["template"] - frame = context["frame"] - hashed_frame = "#" * len(str(frame)) - if ( "{originalBasename}" in template and "frame" in context ): + frame = context["frame"] + hashed_frame = "#" * len(str(frame)) origin_basename = context["originalBasename"] context["originalBasename"] = origin_basename.replace( frame, hashed_frame From 6a8f40f5bb29e8b1bd04497cffe433fc1792af3c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Feb 2023 14:27:42 +0100 Subject: [PATCH 280/281] anatomy data from instance rather then context --- .../plugins/publish/collect_otio_subset_resources.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index dab52986da..f659791d95 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -22,7 +22,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): """Get Resources for a subset version""" label = "Collect OTIO Subset Resources" - order = pyblish.api.CollectorOrder + 0.0021 + order = pyblish.api.CollectorOrder + 0.491 families = ["clip"] hosts = ["resolve", "hiero", "flame"] @@ -50,9 +50,9 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] - otio_avalable_range = otio_clip.available_range() - media_fps = otio_avalable_range.start_time.rate - available_duration = otio_avalable_range.duration.value + otio_available_range = otio_clip.available_range() + media_fps = otio_available_range.start_time.rate + available_duration = otio_available_range.duration.value # get available range trimmed with processed retimes retimed_attributes = get_media_range_with_retimes( @@ -248,7 +248,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # Task can be optional in anatomy data host_name = context.data["hostName"] family = instance.data["family"] - anatomy_data = instance.context.data["anatomyData"] + anatomy_data = instance.data["anatomyData"] task_info = anatomy_data.get("task") or {} return get_publish_template_name( @@ -259,4 +259,4 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): task_type=task_info.get("type"), project_settings=context.data["project_settings"], logger=self.log - ) \ No newline at end of file + ) From 24715ff2de8e8c673670643ebb9263f3bd7219e3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 12:34:45 +0100 Subject: [PATCH 281/281] global: template should not have frame or other optional elements in tempate, since they are already part of the `originalBasename` --- openpype/settings/defaults/project_anatomy/templates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 32230e0625..02c0e35377 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -55,7 +55,7 @@ }, "source": { "folder": "{root[work]}/{originalDirname}", - "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", + "file": "{originalBasename}.{ext}", "path": "{@folder}/{@file}" }, "__dynamic_keys_labels__": { @@ -66,4 +66,4 @@ "source": "source" } } -} \ No newline at end of file +}