From f357a8633ce605f0cba8db867b1b3f5d34f6c36c Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 4 Aug 2023 10:00:44 +0200 Subject: [PATCH 01/84] add warning message if target task is different from current task --- openpype/tools/workfiles/save_as_dialog.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 9f1d1060da8..3fdbce4aef6 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -9,6 +9,7 @@ registered_host, legacy_io, ) +from openpype.pipeline.context_tools import get_current_task_name from openpype.pipeline.workfile import get_last_workfile_with_version from openpype.pipeline.template_data import get_template_data_with_names from openpype.tools.utils import PlaceholderLineEdit @@ -234,6 +235,14 @@ def __init__( # Preview widget preview_label = QtWidgets.QLabel("Preview filename", inputs_widget) + current_task_name = get_current_task_name() + target_task_name = self.data.get("task").get("name") + warning_label = QtWidgets.QLabel( + "Warning: You are saving to a different task " + "than the current one. " + "Current task: {}. Target task: {}" + "".format(current_task_name, target_task_name) + ) # Subversion input subversion = SubversionLineEdit(inputs_widget) @@ -281,6 +290,8 @@ def __init__( subversion.setVisible(False) inputs_layout.addRow("Extension:", ext_combo) inputs_layout.addRow("Preview:", preview_label) + if current_task_name != target_task_name: + inputs_layout.addRow(warning_label) # Build layout main_layout = QtWidgets.QVBoxLayout(self) @@ -315,6 +326,7 @@ def __init__( self.last_version_check = last_version_check self.preview_label = preview_label + self.warning_label = warning_label self.subversion = subversion self.ext_combo = ext_combo self._ext_delegate = ext_delegate From da782b70b19b079ed1f0c684cc9ae23537e883bf Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 22 Aug 2023 14:30:39 +0200 Subject: [PATCH 02/84] add a message when selected asset is different --- openpype/tools/workfiles/save_as_dialog.py | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 3fdbce4aef6..7c8690f4104 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -9,7 +9,12 @@ registered_host, legacy_io, ) -from openpype.pipeline.context_tools import get_current_task_name +from openpype.pipeline.context_tools import ( + get_current_task_name, + get_current_asset_name, + get_current_context, + get_global_context +) from openpype.pipeline.workfile import get_last_workfile_with_version from openpype.pipeline.template_data import get_template_data_with_names from openpype.tools.utils import PlaceholderLineEdit @@ -237,13 +242,20 @@ def __init__( preview_label = QtWidgets.QLabel("Preview filename", inputs_widget) current_task_name = get_current_task_name() target_task_name = self.data.get("task").get("name") - warning_label = QtWidgets.QLabel( + current_asset_name = get_current_asset_name() + target_asset_name = self.data.get("asset") + task_warning_label = QtWidgets.QLabel( "Warning: You are saving to a different task " "than the current one. " "Current task: {}. Target task: {}" "".format(current_task_name, target_task_name) ) - + asset_warning_label = QtWidgets.QLabel( + "Warning: You are saving to a different asset " + "than the current one. " + "Current asset: {}. Target asset: {}" + "".format(current_asset_name, target_asset_name) + ) # Subversion input subversion = SubversionLineEdit(inputs_widget) subversion.set_placeholder("Will be part of filename.") @@ -290,8 +302,10 @@ def __init__( subversion.setVisible(False) inputs_layout.addRow("Extension:", ext_combo) inputs_layout.addRow("Preview:", preview_label) + if current_asset_name != target_asset_name: + inputs_layout.addRow(asset_warning_label) if current_task_name != target_task_name: - inputs_layout.addRow(warning_label) + inputs_layout.addRow(task_warning_label) # Build layout main_layout = QtWidgets.QVBoxLayout(self) @@ -326,7 +340,8 @@ def __init__( self.last_version_check = last_version_check self.preview_label = preview_label - self.warning_label = warning_label + self.task_warning_label = task_warning_label + self.asset_warning_label = asset_warning_label self.subversion = subversion self.ext_combo = ext_combo self._ext_delegate = ext_delegate From 2fa95f0fbcae47e7cb798f9acffc65b3fdc2ce18 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 10:15:15 +0200 Subject: [PATCH 03/84] add job name setting for deadline --- openpype/settings/defaults/project_settings/deadline.json | 1 + .../schemas/projects_schema/schema_project_deadline.json | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 1b8c8397d76..058c8cf1a3d 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -1,5 +1,6 @@ { "deadline_servers": [], + "deadline_job_name": "{asset}_{task[name]}_{version}_{subversion}.{ext}", "publish": { "CollectDefaultDeadlineServer": { "pass_mongo_url": true 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 6d59b5a92b4..471dae97375 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -11,6 +11,11 @@ "label": "Deadline Webservice URLs", "multiselect": true }, + { + "type": "text", + "key": "deadline_job_name", + "label": "Job name" + }, { "type": "dict", "collapsible": true, From e83763b3f6b5441f4e6b6cb3088a64dcd600b752 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:27:49 +0200 Subject: [PATCH 04/84] create function to generate batch name from settings --- openpype/modules/deadline/utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 openpype/modules/deadline/utils.py diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py new file mode 100644 index 00000000000..7b898bcfd20 --- /dev/null +++ b/openpype/modules/deadline/utils.py @@ -0,0 +1,24 @@ +from openpype.settings import get_current_project_settings + + +def set_batch_name(instance, filename): + context = instance.context + subversion = filename.split("_")[-1].split(".")[0] + anatomy_data = context.data.get("anatomyData") + + formatting_data = { + "asset": anatomy_data.get("asset"), + "task": anatomy_data.get("task"), + "subset": instance.data.get("subset"), + "version": "v" + str(instance.data.get("version")).zfill(3), + "project": anatomy_data.get("project"), + "family": instance.data.get("family"), + "comment": instance.data.get("comment"), + "subversion": subversion or '', + "ext": "ma" + } + + batch_name_settings = get_current_project_settings()["deadline"]["deadline_job_name"] # noqa + batch_name = batch_name_settings.format(**formatting_data) + + return batch_name From 32f500f6f8e84564bace4f8b849855a3959d3543 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:28:49 +0200 Subject: [PATCH 05/84] set batch name from settings for maya --- .../deadline/plugins/publish/submit_maya_deadline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a6cdcb7e711..f6f220d3f07 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -37,6 +37,7 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo +from openpype.modules.deadline.utils import set_batch_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -124,11 +125,13 @@ def get_job_info(self): src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) + batch_name = set_batch_name(instance, src_filename) + if is_in_tests(): - src_filename += datetime.now().strftime("%d%m%Y%H%M%S") + batch_name += datetime.now().strftime("%d%m%Y%H%M%S") job_info.Name = "%s - %s" % (src_filename, instance.name) - job_info.BatchName = src_filename + job_info.BatchName = batch_name job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) From f3e4ee183357b354adbdfedc60cccfec62954fe2 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:31:59 +0200 Subject: [PATCH 06/84] set batch name from settings for after effects --- .../deadline/plugins/publish/submit_aftereffects_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 83dd5b49e24..ec481ec81e7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -11,6 +11,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.modules.deadline.utils import set_batch_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -50,8 +51,9 @@ def get_job_info(self): dln_job_info = DeadlineJobInfo(Plugin="AfterEffects") context = self._instance.context + filename = os.path.basename(self._instance.data["source"]) - batch_name = os.path.basename(self._instance.data["source"]) + batch_name = set_batch_name(self._instance, filename) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") dln_job_info.Name = self._instance.data["name"] From 151853c4a93a949bf7b89277a70ed15e33719bf7 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:34:33 +0200 Subject: [PATCH 07/84] set batch name from settings for celaction --- .../deadline/plugins/publish/submit_celaction_deadline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index ee28612b44d..2f9bbfa6395 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -5,6 +5,8 @@ import requests import pyblish.api +from openpype.modules.deadline.utils import set_batch_name + class CelactionSubmitDeadline(pyblish.api.InstancePlugin): """Submit CelAction2D scene to Deadline @@ -74,6 +76,8 @@ def payload_submit(self, render_path = os.path.normpath(render_path) script_name = os.path.basename(script_path) + batch_name = set_batch_name(instance, script_name) + for item in instance.context: if "workfile" in item.data["family"]: msg = "Workfile (scene) must be published along" @@ -136,7 +140,7 @@ def payload_submit(self, "Plugin": "CelAction", # Top-level group name - "BatchName": script_name, + "BatchName": batch_name, # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, From 4dd72f0623cfba53d9b4dc2811b0582121609900 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:37:03 +0200 Subject: [PATCH 08/84] set batch name from settings for fusion --- .../deadline/plugins/publish/submit_fusion_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index a48596c6bf0..868e953dc94 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -14,6 +14,7 @@ BoolDef, NumberDef ) +from openpype.modules.deadline.utils import set_batch_name class FusionSubmitDeadline( @@ -141,6 +142,7 @@ def process(self, instance): ) filename = os.path.basename(script_path) + batch_name = set_batch_name(instance, filename) # Documentation for keys available at: # https://docs.thinkboxsoftware.com @@ -149,7 +151,7 @@ def process(self, instance): payload = { "JobInfo": { # Top-level group name - "BatchName": filename, + "BatchName": batch_name, # Asset dependency to wait for at least the scene file to sync. "AssetDependency0": script_path, From 01cbb2eea13f34805227755c0ec9b96b6b407aa8 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:38:49 +0200 Subject: [PATCH 09/84] set batch name from settings for harmony --- .../deadline/plugins/publish/submit_harmony_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 84fca11d9d2..6c5330b5f4a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -13,6 +13,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.modules.deadline.utils import set_batch_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -264,7 +265,8 @@ def get_job_info(self): job_info.Pool = self._instance.data.get("primaryPool") job_info.SecondaryPool = self._instance.data.get("secondaryPool") job_info.ChunkSize = self.chunk_size - batch_name = os.path.basename(self._instance.data["source"]) + filename = os.path.basename(self._instance.data["source"]) + batch_name = set_batch_name(self._instance, filename) if is_in_tests: batch_name += datetime.now().strftime("%d%m%Y%H%M%S") job_info.BatchName = batch_name From 4a14385e80860b67b459995b9a1b3cf342a664ad Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:44:17 +0200 Subject: [PATCH 10/84] set batch name from settings for houdini --- .../plugins/publish/submit_houdini_render_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 254914a8505..6f71ae55126 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 @@ from openpype.tests.lib import is_in_tests from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo +from openpype.modules.deadline.utils import set_batch_name from openpype.lib import is_running_from_build @@ -55,8 +56,10 @@ def get_job_info(self): filepath = context.data["currentFile"] filename = os.path.basename(filepath) + batch_name = set_batch_name(instance, filename) + job_info.Name = "{} - {}".format(filename, instance.name) - job_info.BatchName = filename + job_info.BatchName = batch_name job_info.Plugin = "Houdini" job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) From 7d4f856632c2b2c029220634ab927c0cf207b96c Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:45:52 +0200 Subject: [PATCH 11/84] set batch name from settings for max --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index b6a30e36b71..b66e26eff5f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -20,6 +20,7 @@ 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 +from openpype.modules.deadline.utils import set_batch_name @attr.s @@ -74,8 +75,10 @@ def get_job_info(self): src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) + batch_name = set_batch_name(instance, src_filename) + job_info.Name = "%s - %s" % (src_filename, instance.name) - job_info.BatchName = src_filename + job_info.BatchName = batch_name job_info.Plugin = instance.data["plugin"] job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) job_info.EnableAutoTimeout = True From 41b3caa2d3bd7a5266f21ca8a28ca1332201ef06 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:47:16 +0200 Subject: [PATCH 12/84] set batch name from settings for maya remote --- .../plugins/publish/submit_maya_remote_publish_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 25f859554fa..4c87394f4d2 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 @@ -8,6 +8,7 @@ from openpype.settings import get_project_settings from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build +from openpype.modules.deadline.utils import set_batch_name import pyblish.api @@ -58,8 +59,7 @@ def process(self, instance): scenename = os.path.basename(scene) job_name = "{scene} [PUBLISH]".format(scene=scenename) - batch_name = "{code} - {scene}".format(code=project_name, - scene=scenename) + batch_name = set_batch_name(instance, scenename) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") From 080727cc72d328b0d40a95d8bfe4d884c7c41a9c Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 15:49:18 +0200 Subject: [PATCH 13/84] set batch name from settings for nuke --- .../deadline/plugins/publish/submit_nuke_deadline.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 49002317835..60b84d3cfe8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -18,6 +18,7 @@ BoolDef, NumberDef ) +from openpype.modules.deadline.utils import set_batch_name class NukeSubmitDeadline(pyblish.api.InstancePlugin, @@ -200,12 +201,13 @@ def payload_submit( response_data=None ): render_dir = os.path.normpath(os.path.dirname(render_path)) - batch_name = os.path.basename(script_path) - jobname = "%s - %s" % (batch_name, instance.name) + filename = os.path.basename(script_path) + jobname = "%s - %s" % (filename, instance.name) + + batch_name = set_batch_name(instance, filename) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") - output_filename_0 = self.preview_fname(render_path) if not response_data: From b9482f6715074fc4a57111ab74607ef43118cef8 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 16:19:44 +0200 Subject: [PATCH 14/84] add doc for deadline batch name setting --- website/docs/assets/deadline_batch_name.png | Bin 0 -> 11813 bytes website/docs/module_deadline.md | 27 ++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 website/docs/assets/deadline_batch_name.png diff --git a/website/docs/assets/deadline_batch_name.png b/website/docs/assets/deadline_batch_name.png new file mode 100644 index 0000000000000000000000000000000000000000..7928237d703157ea94a43e10d75df9da4e7de22e GIT binary patch literal 11813 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 +
+ + +| Context key | Description | +| --- | --- | +| `project[name]` | Project's full name | +| `project[code]` | Project's code | +| `asset` | Name of asset or shot | +| `task[name]` | Name of task | +| `task[type]` | Type of task | +| `task[short]` | Short name of task type (eg. 'Modeling' > 'mdl') | +| `version` | Version number | +| `family` | Main family name | +| `ext` | File extension | +| `subversion` | File name's subversion +| `comment` | | + +
+
+ ## Configuration From 0e223090c8f5ff6c26afec92dd631da8f83ecb59 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Fri, 29 Sep 2023 17:07:28 +0200 Subject: [PATCH 15/84] remove double _ from batch name if subversion doesn't exsit --- openpype/modules/deadline/utils.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index 7b898bcfd20..a30dace4dd7 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -1,24 +1,37 @@ +import os +import re from openpype.settings import get_current_project_settings def set_batch_name(instance, filename): context = instance.context - subversion = filename.split("_")[-1].split(".")[0] + basename, ext = os.path.splitext(filename) + subversion = basename.split("_")[-1] + version = "v" + str(instance.data.get("version")).zfill(3) + + if subversion == version: + subversion = "" + anatomy_data = context.data.get("anatomyData") formatting_data = { "asset": anatomy_data.get("asset"), "task": anatomy_data.get("task"), "subset": instance.data.get("subset"), - "version": "v" + str(instance.data.get("version")).zfill(3), + "version": version, "project": anatomy_data.get("project"), "family": instance.data.get("family"), "comment": instance.data.get("comment"), - "subversion": subversion or '', - "ext": "ma" + "subversion": subversion, + "ext": ext[1:] } batch_name_settings = get_current_project_settings()["deadline"]["deadline_job_name"] # noqa batch_name = batch_name_settings.format(**formatting_data) + for m in re.finditer("__", batch_name): + batch_name_list = list(batch_name) + batch_name_list.pop(m.start()) + batch_name = "".join(batch_name_list) + return batch_name From 5422f3a3187884de6c5d5ab0ecd9687295de3f63 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 Oct 2023 12:05:26 +0200 Subject: [PATCH 16/84] add a group limit setting in maya render set to send group limits on dealine for the job --- openpype/hosts/maya/api/lib.py | 21 ++++++++- .../maya/plugins/create/create_render.py | 6 +++ .../maya/plugins/publish/collect_render.py | 47 +++++++++++++++++++ openpype/modules/deadline/deadline_module.py | 31 ++++++++++++ .../plugins/publish/submit_maya_deadline.py | 2 + 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index fca4410ede1..ba0f049fd93 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -421,11 +421,28 @@ def imprint(node, data): add_type = {"attributeType": "enum", "enumName": ":".join(value)} set_type = {"keyable": False, "channelBox": True} value = 0 # enum default + elif isinstance(value, dict): + for key, group_list in value.items(): + cmds.addAttr( + node, + longName=key, + numberOfChildren=len(group_list), + attributeType="compound" + ) + for group in group_list: + cmds.addAttr( + node, + longName=group, + attributeType="bool", + parent=key + ) + continue else: raise TypeError("Unsupported type: %r" % type(value)) - cmds.addAttr(node, longName=key, **add_type) - cmds.setAttr(node + "." + key, value, **set_type) + if not isinstance(value, dict): + cmds.addAttr(node, longName=key, **add_type) + cmds.setAttr(node + "." + key, value, **set_type) def lsattr(attr, value=None): diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 46811758086..6e66003c7c9 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -297,6 +297,12 @@ def _create_render_settings(self): self.data["secondaryPool"] = self._set_default_pool(pool_names, secondary_pool) + limit_groups = self.deadline_module.get_deadline_limit_groups( + deadline_url, + self.log + ) + self.data["limits"] = {"limits": limit_groups} + if muster_enabled: self.log.info(">>> Loading Muster credentials ...") self._load_credentials() diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index babd494758f..f1371fa6204 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -51,6 +51,8 @@ from openpype.lib import get_formatted_current_time from openpype.pipeline import legacy_io +from openpype.settings import get_system_settings, get_project_settings +from openpype.modules import ModulesManager from openpype.hosts.maya.api.lib_renderproducts import get as get_layer_render_products # noqa: E501 from openpype.hosts.maya.api import lib @@ -426,6 +428,9 @@ def parse_options(self, render_globals): options["mayaRenderPlugin"] = maya_render_plugin + limits = self._get_checked_limit_groups(attributes) + options["limits"] = limits + return options def _discover_pools(self, attributes): @@ -467,3 +472,45 @@ def get_render_attribute(attr, layer): return lib.get_attr_in_layer( "defaultRenderGlobals.{}".format(attr), layer=layer ) + + def _get_checked_limit_groups(self, attributes): + self.log.debug(f"SELF METHODS: {dir(self)}") + checked_limits = [] + deadline_settings = get_system_settings()["modules"]["deadline"] + + if not deadline_settings["enabled"]: + return checked_limits + + manager = ModulesManager() + deadline_module = manager.modules_by_name["deadline"] + + try: + default_servers = deadline_settings["deadline_urls"] + project_settings = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + project_servers = ( + project_settings["deadline"]["deadline_servers"] + ) + deadline_servers = { + k: default_servers[k] + for k in project_servers + if k in default_servers + } + + if not deadline_servers: + deadline_servers = default_servers + except AttributeError: + # Handle situation were we had only one url for deadline. + # get default deadline webservice url from deadline module + deadline_servers = deadline_module.deadline_urls + + limit_groups = deadline_module.get_deadline_limit_groups( + deadline_settings['deadline_urls']["default"], + self.log + ) + for group, value in zip(limit_groups, attributes['limits'][0]): + if value is True: + checked_limits.append(group) + + return checked_limits diff --git a/openpype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py index 9855f8c1b10..b73872b9ef4 100644 --- a/openpype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -74,3 +74,34 @@ def get_deadline_pools(webservice, log=None): return [] return response.json() + + @staticmethod + def get_deadline_limit_groups(webservice, log=None): + """Get Limits groups for Deadlin + Args: + webservice (str): Server url + log (Logger) + Returns: + list: Limits + Throws: + RuntimeError: If Deadline webservice is unreachable. + """ + if not log: + log = Logger.get_logger(__name__) + + argument = "{}/api/limitgroups?NamesOnly=true".format(webservice) + try: + response = requests_get(argument) + except requests.exceptions.ConnectionError as exc: + msg = "Cannot connect to DL web service {}".format(webservice) + log.error(msg) + six.reraise( + DeadlineWebserviceError, + DeadlineWebserviceError("{} - {}".format(msg, exc)), + sys.exc_info()[2] + ) + if not response.ok: + log.warning("No limit group retrieved") + return [] + + return response.json() diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a6cdcb7e711..763d2ff5741 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -148,6 +148,8 @@ def get_job_info(self): if self.group != "none" and self.group: job_info.Group = self.group + self.limit = instance.data.get('limits') + if self.limit: job_info.LimitGroups = ",".join(self.limit) From 83eff2a82ed3141d880e3d698b16dcecebed392d Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 3 Jul 2023 17:36:48 +0300 Subject: [PATCH 17/84] add select invalid action --- .../publish/validate_sop_output_node.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index ed7f4387294..74f45c09250 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -1,7 +1,13 @@ # -*- coding: utf-8 -*- import pyblish.api from openpype.pipeline import PublishValidationError +from openpype.pipeline.publish import RepairAction +import hou + +class SelectInvalidAction(RepairAction): + label = "Select Invalid ROP" + icon = "mdi.cursor-default-click" class ValidateSopOutputNode(pyblish.api.InstancePlugin): """Validate the instance SOP Output Node. @@ -19,6 +25,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" + actions = [SelectInvalidAction] def process(self, instance): @@ -31,9 +38,6 @@ def process(self, instance): @classmethod def get_invalid(cls, instance): - - import hou - output_node = instance.data.get("output_node") if output_node is None: @@ -81,3 +85,19 @@ def get_invalid(cls, instance): "Output node `%s` has no geometry data." % output_node.path() ) return [output_node.path()] + + @classmethod + def repair(cls, instance): + """Select Invalid ROP. + + It's used to select invalid ROP which tells the + artist which ROP node need to be fixed! + """ + + rop_node = hou.node(instance.data["instance_node"]) + rop_node.setSelected(True, clear_all_selected=True) + + cls.log.debug( + '%s has been selected' + % rop_node.path() + ) From 59f41478036d865340378a5769a28b89ae586921 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Mon, 3 Jul 2023 18:16:23 +0300 Subject: [PATCH 18/84] fix lint problem --- .../hosts/houdini/plugins/publish/validate_sop_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 74f45c09250..e8fb11a51ce 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -5,6 +5,7 @@ import hou + class SelectInvalidAction(RepairAction): label = "Select Invalid ROP" icon = "mdi.cursor-default-click" From 71c933fc931ac2753fee834909e931840ee79aa0 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 13:27:31 +0300 Subject: [PATCH 19/84] change action name to Select ROP --- .../plugins/publish/validate_sop_output_node.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index e8fb11a51ce..0d2aa64df6d 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -6,8 +6,8 @@ import hou -class SelectInvalidAction(RepairAction): - label = "Select Invalid ROP" +class SelectROPAction(RepairAction): + label = "Select ROP" icon = "mdi.cursor-default-click" class ValidateSopOutputNode(pyblish.api.InstancePlugin): @@ -26,7 +26,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" - actions = [SelectInvalidAction] + actions = [SelectROPAction] def process(self, instance): @@ -89,10 +89,10 @@ def get_invalid(cls, instance): @classmethod def repair(cls, instance): - """Select Invalid ROP. + """Select ROP. - It's used to select invalid ROP which tells the - artist which ROP node need to be fixed! + It's used to select the associated ROP for the selected instance + which tells the artist which ROP node need to be fixed! """ rop_node = hou.node(instance.data["instance_node"]) From 9a5adbb0df297ab362b7c1e66fd60782c30ffac5 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 13:29:24 +0300 Subject: [PATCH 20/84] add SelectInvalidAction --- .../plugins/publish/validate_sop_output_node.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 0d2aa64df6d..834bc39a249 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -2,6 +2,7 @@ import pyblish.api from openpype.pipeline import PublishValidationError from openpype.pipeline.publish import RepairAction +from openpype.hosts.houdini.api.action import SelectInvalidAction import hou @@ -26,7 +27,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin): families = ["pointcache", "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" - actions = [SelectROPAction] + actions = [SelectROPAction, SelectInvalidAction] def process(self, instance): @@ -48,7 +49,7 @@ def get_invalid(cls, instance): "Ensure a valid SOP output path is set." % node.path() ) - return [node.path()] + return [node] # Output node must be a Sop node. if not isinstance(output_node, hou.SopNode): @@ -58,7 +59,7 @@ def get_invalid(cls, instance): "instead found category type: %s" % (output_node.path(), output_node.type().category().name()) ) - return [output_node.path()] + return [output_node] # For the sake of completeness also assert the category type # is Sop to avoid potential edge case scenarios even though @@ -78,14 +79,14 @@ def get_invalid(cls, instance): except hou.Error as exc: cls.log.error("Cook failed: %s" % exc) cls.log.error(output_node.errors()[0]) - return [output_node.path()] + return [output_node] # Ensure the output node has at least Geometry data if not output_node.geometry(): cls.log.error( "Output node `%s` has no geometry data." % output_node.path() ) - return [output_node.path()] + return [output_node] @classmethod def repair(cls, instance): From 54c04b948eaa668240bb02a7880bb79b30efe8e6 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 17:08:14 +0300 Subject: [PATCH 21/84] move SelectRopAction to api.actions --- openpype/hosts/houdini/api/action.py | 43 +++++++++++++++++++ .../publish/validate_sop_output_node.py | 26 ++--------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index b1519ddd1d9..333902c15fe 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -42,3 +42,46 @@ def process(self, context, plugin): node.setCurrent(True) else: self.log.info("No invalid nodes found.") + + +class SelectROPAction(pyblish.api.Action): + """Select ROP. + + It's used to select the associated ROPs with all errored instances + not necessarily the ones that errored on the plugin we're running the action on. + """ + + label = "Select ROP" + on = "failed" # This action is only available on a failed plug-in + icon = "mdi.cursor-default-click" + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding ROP nodes..") + rop_nodes = list() + for instance in instances: + node_path = instance.data.get("instance_node") + if not node_path: + continue + + node = hou.node(node_path) + if not node: + continue + + rop_nodes.append(node) + + hou.clearAllSelected() + if rop_nodes: + self.log.info("Selecting ROP nodes: {}".format( + ", ".join(node.path() for node in rop_nodes) + )) + for node in rop_nodes: + node.setSelected(True) + node.setCurrent(True) + else: + self.log.info("No ROP nodes found.") diff --git a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py index 834bc39a249..d9dee38680d 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_sop_output_node.py @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- import pyblish.api from openpype.pipeline import PublishValidationError -from openpype.pipeline.publish import RepairAction -from openpype.hosts.houdini.api.action import SelectInvalidAction +from openpype.hosts.houdini.api.action import ( + SelectInvalidAction, + SelectROPAction, +) import hou -class SelectROPAction(RepairAction): - label = "Select ROP" - icon = "mdi.cursor-default-click" - class ValidateSopOutputNode(pyblish.api.InstancePlugin): """Validate the instance SOP Output Node. @@ -87,19 +85,3 @@ def get_invalid(cls, instance): "Output node `%s` has no geometry data." % output_node.path() ) return [output_node] - - @classmethod - def repair(cls, instance): - """Select ROP. - - It's used to select the associated ROP for the selected instance - which tells the artist which ROP node need to be fixed! - """ - - rop_node = hou.node(instance.data["instance_node"]) - rop_node.setSelected(True, clear_all_selected=True) - - cls.log.debug( - '%s has been selected' - % rop_node.path() - ) From 9a17ba1ea8c2182003d03d8f049e2b18ea8a2cb9 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Tue, 4 Jul 2023 17:11:21 +0300 Subject: [PATCH 22/84] fix lint problems --- openpype/hosts/houdini/api/action.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index 333902c15fe..65d38480042 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -47,8 +47,7 @@ def process(self, context, plugin): class SelectROPAction(pyblish.api.Action): """Select ROP. - It's used to select the associated ROPs with all errored instances - not necessarily the ones that errored on the plugin we're running the action on. + It's used to select the associated ROPs with the errored instances. """ label = "Select ROP" From 7240465c584f460a41acc4eff2b907bf11bac230 Mon Sep 17 00:00:00 2001 From: Mustafa-Zarkash Date: Wed, 5 Jul 2023 14:06:58 +0300 Subject: [PATCH 23/84] update action with roy's PR --- openpype/hosts/houdini/api/action.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/api/action.py b/openpype/hosts/houdini/api/action.py index 65d38480042..77966d6d5c9 100644 --- a/openpype/hosts/houdini/api/action.py +++ b/openpype/hosts/houdini/api/action.py @@ -55,15 +55,12 @@ class SelectROPAction(pyblish.api.Action): icon = "mdi.cursor-default-click" def process(self, context, plugin): - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + errored_instances = get_errored_instances_from_context(context, plugin) # Get the invalid nodes for the plug-ins self.log.info("Finding ROP nodes..") rop_nodes = list() - for instance in instances: + for instance in errored_instances: node_path = instance.data.get("instance_node") if not node_path: continue From 54c5b12a833972e66a2791af1aec527b41682cd2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:12:34 +0800 Subject: [PATCH 24/84] maxscript's conversion of bool to python --- openpype/hosts/max/api/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 14b0653f40e..b2dbaa4e92e 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -176,7 +176,7 @@ def create(self, subset_name, instance_data, pre_create_data): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) - + self.log.debug(f"{instance}") self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -188,6 +188,7 @@ def collect_instances(self): created_instance = CreatedInstance.from_existing( read(rt.GetNodeByName(instance)), self ) + self.log.debug(f"{created_instance}") self._add_instance_to_context(created_instance) def update_instances(self, update_list): From 1f0d614e4c677be725d6fec164ca8f613b6ba54b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:21:37 +0800 Subject: [PATCH 25/84] maxscript's conversion of bool to python --- openpype/hosts/max/api/lib.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 995c35792a6..8aed26fcee3 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -78,7 +78,13 @@ def read(container) -> dict: value.startswith(JSON_PREFIX): with contextlib.suppress(json.JSONDecodeError): value = json.loads(value[len(JSON_PREFIX):]) - data[key.strip()] = value + if key.strip() == "active": + if value == "true": + data[key.strip()] = True + else: + data[key.strip()] = False + else: + data[key.strip()] = value data["instance_node"] = container.Name From ec130c1dc90fa5f7667cff9da5392b89faa36e20 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:22:35 +0800 Subject: [PATCH 26/84] remove unnecessary debug check --- openpype/hosts/max/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index b2dbaa4e92e..fb4a3e5d9c2 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -176,7 +176,6 @@ def create(self, subset_name, instance_data, pre_create_data): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) - self.log.debug(f"{instance}") self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -188,7 +187,6 @@ def collect_instances(self): created_instance = CreatedInstance.from_existing( read(rt.GetNodeByName(instance)), self ) - self.log.debug(f"{created_instance}") self._add_instance_to_context(created_instance) def update_instances(self, update_list): From 36378029cfe789e049e3e72983a0ff81f09e90b0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 13:23:22 +0800 Subject: [PATCH 27/84] restore the plugin.py --- openpype/hosts/max/api/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index fb4a3e5d9c2..14b0653f40e 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -176,6 +176,7 @@ def create(self, subset_name, instance_data, pre_create_data): # Setting the property rt.setProperty( instance_node.openPypeData, "all_handles", node_list) + self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) From 12ae11da1a251f813edc221f92b239e9b08a9b95 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Jun 2023 15:52:37 +0800 Subject: [PATCH 28/84] roy's comment --- openpype/hosts/max/api/lib.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 8aed26fcee3..ccd4cd67e1a 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -78,13 +78,15 @@ def read(container) -> dict: value.startswith(JSON_PREFIX): with contextlib.suppress(json.JSONDecodeError): value = json.loads(value[len(JSON_PREFIX):]) - if key.strip() == "active": - if value == "true": - data[key.strip()] = True - else: - data[key.strip()] = False - else: - data[key.strip()] = value + + # default value behavior + # convert maxscript boolean values + if value == "true": + value = True + elif value == "false": + value = False + + data[key.strip()] = value data["instance_node"] = container.Name From d65e5e69ffb38e41d11ed4ce9a6e4125de068f63 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 09:17:53 +0100 Subject: [PATCH 29/84] Fix collecting arnold prefix when none --- openpype/hosts/maya/api/lib_renderproducts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a6bcd003a54..4f52372f060 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -528,6 +528,9 @@ class RenderProductsArnold(ARenderProducts): def get_renderer_prefix(self): prefix = super(RenderProductsArnold, self).get_renderer_prefix() + if prefix is None: + return "" + merge_aovs = self._get_attr("defaultArnoldDriver.mergeAOVs") if not merge_aovs and "" not in prefix.lower(): # When Merge AOVs is disabled and token not present From 67db5751bc882eff0101ba7c593ef4fc0402eba7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 12:41:26 +0100 Subject: [PATCH 30/84] Revert "Fix collecting arnold prefix when none" This reverts commit ba877956b94b1663ce6d38168eea48da8dd6bfca. --- openpype/hosts/maya/api/lib_renderproducts.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 4f52372f060..a6bcd003a54 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -528,9 +528,6 @@ class RenderProductsArnold(ARenderProducts): def get_renderer_prefix(self): prefix = super(RenderProductsArnold, self).get_renderer_prefix() - if prefix is None: - return "" - merge_aovs = self._get_attr("defaultArnoldDriver.mergeAOVs") if not merge_aovs and "" not in prefix.lower(): # When Merge AOVs is disabled and token not present From 0b491c12560a3a576bdf8840d9819c11bf0d4de2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Jul 2023 12:42:16 +0100 Subject: [PATCH 31/84] Use BigRoy soluiton --- openpype/hosts/maya/api/lib_renderproducts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a6bcd003a54..7bfb53d5006 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -274,12 +274,14 @@ def get_renderer_prefix(self): "Unsupported renderer {}".format(self.renderer) ) + # Note: When this attribute is never set (e.g. on maya launch) then + # this can return None even though it is a string attribute prefix = self._get_attr(prefix_attr) if not prefix: # Fall back to scene name by default - log.debug("Image prefix not set, using ") - file_prefix = "" + log.warning("Image prefix not set, using ") + prefix = "" return prefix From 5b5004000f88e4c3b1e1af4d574df6b4e412b479 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 6 Jul 2023 14:00:30 +0200 Subject: [PATCH 32/84] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3984da09b3cb7261feaa6d901903e5d34d327965 Author: clement.hector Date: Fri Jun 9 11:47:47 2023 +0200 remove print commit 592e08e44d9144efc9b12751420e7654a41787cb Author: clement.hector Date: Fri Jun 9 11:12:41 2023 +0200 fix current project injection commit df06ca431a4b4caa2490ad36d02e479aa6474436 Merge: ce22ea0fc0 ea2d87d903 Author: Clément Hector Date: Wed May 24 09:57:55 2023 +0200 Merge branch 'ynput:develop' into enhancement/310-add-asset-from-library-project-in-placeholder commit ce22ea0fc037e70ee1bd91424b78d2eb8a433427 Author: clement.hector Date: Tue May 9 17:02:56 2023 +0200 fix linter commit 9983460dc7969c0a331d322cfd413402a76f3652 Author: clement.hector Date: Tue May 9 17:01:30 2023 +0200 fix refactoring commit 324ff1137c3aec15e20db4a45361f72b3f2d0dd3 Author: clement.hector Date: Tue May 9 16:34:39 2023 +0200 fix refactoring error commit 1148a85c70294906a4f22d8242e80e4c9552098b Author: clement.hector Date: Tue May 9 16:31:26 2023 +0200 rename fonction get_library_project_names commit e4e40d8be40aa7d57db28ad1d0ed5acfb358d821 Author: clement.hector Date: Tue May 9 11:23:21 2023 +0200 fix linter commit b04d3c148aa598cd7c6198da2ba08d8a3f9dfcb4 Author: clement.hector Date: Tue May 9 10:03:07 2023 +0200 Add library in builder type --- .../workfile/workfile_template_builder.py | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index e1013b26458..93a0d14c1ec 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -24,6 +24,7 @@ get_linked_assets, get_representations, ) +from openpype.client.entities import get_projects from openpype.settings import ( get_project_settings, get_system_settings, @@ -123,9 +124,15 @@ def __init__(self, host): self._linked_asset_docs = None self._task_type = None + self._project_name = legacy_io.active_project() + @property def project_name(self): - return legacy_io.active_project() + return self._project_name + + @project_name.setter + def project_name(self, name): + self._project_name = name @property def current_asset_name(self): @@ -852,6 +859,7 @@ class PlaceholderPlugin(object): def __init__(self, builder): self._builder = builder + self._project_name = self.builder.project_name @property def builder(self): @@ -865,7 +873,11 @@ def builder(self): @property def project_name(self): - return self._builder.project_name + return self._project_name + + @project_name.setter + def project_name(self, name): + self._project_name = name @property def log(self): @@ -1248,6 +1260,14 @@ def get_load_plugin_options(self, options=None): ] loader_items = list(sorted(loader_items, key=lambda i: i["label"])) + libraries_project_items = [ + { + "label": "From Library : {}".format(project_name), + "value": project_name + } + for project_name in get_library_project_names() + ] + options = options or {} # Get families from all loaders excluding "*" @@ -1266,13 +1286,13 @@ def get_load_plugin_options(self, options=None): attribute_definitions.EnumDef( "builder_type", - label="Asset Builder Type", + label="Asset Builder Source", default=options.get("builder_type"), items=[ - {"label": "Current asset", "value": "context_asset"}, - {"label": "Linked assets", "value": "linked_asset"}, - {"label": "All assets", "value": "all_assets"}, - ], + {"label": "From Current asset", "value": "context_asset"}, + {"label": "From Linked assets", "value": "linked_asset"}, + {"label": "From Others assets", "value": "all_assets"}, + ] + libraries_project_items, tooltip=( "Asset Builder Type\n" "\nBuilder type describe what template loader will look" @@ -1283,6 +1303,10 @@ def get_load_plugin_options(self, options=None): " linked to current context asset." "\nLinked asset are looked in database under" " field \"inputLinks\"" + "\nAll assets : Template loader will look for all assets" + " in database." + "\nLibraries assets : Template loader will look for assets" + "in libraries." ) ), attribute_definitions.EnumDef( @@ -1415,7 +1439,7 @@ def _get_representations(self, placeholder): from placeholder data. """ - project_name = self.builder.project_name + self.project_name = self.builder.project_name current_asset_doc = self.builder.current_asset_doc linked_asset_docs = self.builder.linked_asset_docs @@ -1444,8 +1468,9 @@ def _get_representations(self, placeholder): "representation": [placeholder.data["representation"]], "family": [placeholder.data["family"]], } - else: + if builder_type != "all_assets": + self.project_name = builder_type context_filters = { "asset": [re.compile(placeholder.data["asset"])], "subset": [re.compile(placeholder.data["subset"])], @@ -1455,7 +1480,7 @@ def _get_representations(self, placeholder): } return list(get_representations( - project_name, + self.project_name, context_filters=context_filters )) @@ -1544,7 +1569,7 @@ def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): placeholder, representation ) self.log.info( - "Loading {} from {} with loader {}\n" + "Loading {} from {} with loader {} with" "Loader arguments used : {}".format( repre_context["subset"], repre_context["asset"], @@ -1843,3 +1868,13 @@ def get_errors(self): def create_failed(self, creator_data): self._failed_created_publish_instances.append(creator_data) + + +def get_library_project_names(): + libraries = list() + + for project in get_projects(fields=["name", "data.library_project"]): + if project.get("data", {}).get("library_project", False): + libraries.append(project["name"]) + + return libraries From e3d47e14174d5c492c55fa3a6d847738087759dd Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 6 Jul 2023 14:12:44 +0200 Subject: [PATCH 33/84] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1c198603e87a3896ac137bf742d719fc6966a8fb Merge: d96495e8d3 b538efdcdd Author: clement.hector Date: Thu Jul 6 14:06:57 2023 +0200 Merge remote-tracking branch 'upstream/develop' into 483-merge-feature-345-and-389-to-set-frame-range-on-animation-instance commit d96495e8d3ee25a1bdb007e45c4fd3dc062e892a Author: clement.hector Date: Tue Jul 4 17:43:36 2023 +0200 add update instances asset name commit 0ec7f993bdbd2201f2815cd38297a672ae7f6068 Author: Clément Hector Date: Tue Jul 4 15:53:46 2023 +0200 Update openpype/hosts/maya/api/lib.py Co-authored-by: Roy Nieterau commit 359bc654a7cb43eb108e69e0cf3072491ec2f57e Author: Clément Hector Date: Tue Jul 4 15:53:15 2023 +0200 Update openpype/hosts/maya/api/lib.py Co-authored-by: Roy Nieterau commit 248a8f616d7fe1a962e416d9d7032aa58d7e81fd Author: clement.hector Date: Tue Jul 4 11:04:46 2023 +0200 iter over instance commit 8c4e0b7fa1351079a8626f717e697dadec15c06c Author: clement.hector Date: Mon Jul 3 15:39:52 2023 +0200 extract asset update from framerange commit 399bbec6214c77c2684a456c7be17f44698558ab Author: clement.hector Date: Mon Jul 3 12:01:55 2023 +0200 revert update instance option commit 079abd03b896bcd702d96c3427b35e1169f537ef Author: clement.hector Date: Mon Jul 3 11:54:37 2023 +0200 update instance frame range commit 95b5fff890360b0e8d414b2e66b1810a383595bd Author: clement.hector Date: Mon Jul 3 11:49:58 2023 +0200 update instance frame range commit 6296fc52d956cfe83500b6d9e17468ea546ca403 Author: Clément Hector Date: Mon Jul 3 11:43:16 2023 +0200 Update openpype/hosts/maya/api/lib.py Co-authored-by: Roy Nieterau commit 435deedd488a7122cf0708908e8b5b7f33eac28a Author: clement.hector Date: Wed Jun 28 22:16:03 2023 +0200 cosmetic fix commit de4bad20c222168baff8a34cdd48cc953398897a Author: clement.hector Date: Wed Jun 28 22:14:39 2023 +0200 Change reset_frame_range default to false commit 921eb4fcfc24512e47f2f7e680b71e4b677697dd Author: clement.hector Date: Wed Jun 28 17:00:52 2023 +0200 add reset_frame_range instance=true commit 062cf52f64b50fd7d7be55d69c80bf3d026f9574 Author: clement.hector Date: Wed Jun 28 16:42:10 2023 +0200 remove rendre check commit 002f2c860a2e08f12cb30e1f2cf506bbd340c704 Author: Clément Hector Date: Wed Jun 28 16:36:05 2023 +0200 Update openpype/hosts/maya/api/lib.py Co-authored-by: Roy Nieterau commit 92f5046b357509b8b6f286e7314f0c39b12a8bca Author: clement.hector Date: Tue Jun 27 20:23:23 2023 +0200 commit 7c2bb2d Author: Thomas Fricard Date: Mon May 15 17:12:13 2023 +0200 cosmetic refactoring commit 6fcb0a6 Author: Thomas Fricard Date: Mon May 15 16:55:19 2023 +0200 check id attribute before family attribute commit df6cc8c Author: Thomas Fricard Date: Fri May 12 16:01:52 2023 +0200 verify that 'family' attributes exists and equals to 'render' commit 5ae1847 Author: Thomas Fricard Date: Fri May 12 15:58:05 2023 +0200 change function name + docstrings commit ff09bbb Author: Thomas Fricard Date: Fri May 12 11:48:43 2023 +0200 add a 'type' key to the 'include handles' setting of maya commit ec96832 Author: Thomas Fricard Date: Wed May 3 15:32:06 2023 +0200 merge and refactor functions to updte asset frame range commit 661f494 Author: Thomas Fricard Date: Tue Apr 18 10:03:24 2023 +0200 add 'instances' to reset_frame_range function arguments and make sure it does not update frame range instances on creating render settings commit a10ecfc Merge: 19836b3 196f698 Author: Thomas Fricard Date: Tue Apr 18 09:32:32 2023 +0200 Merge branch '346-fix-studio-openpype-udpate-frame-range-of-scene-doesn-t-update-frame-range-in-animation-instance' of github.com:quadproduction/OpenPype into 346-fix-studio-openpype-udpate-frame-range-of-scene-doesn-t-update-frame-range-in-animation-instance commit 19836b3 Author: Thomas Fricard Date: Tue Apr 18 09:31:27 2023 +0200 change key and label to make the setting more understandable commit 196f698 Merge: 155a0c2 515c0ac Author: Thomas Fricard <51854004+friquette@users.noreply.github.com> Date: Thu Apr 13 11:06:26 2023 +0200 Merge branch 'develop' into 346-fix-studio-openpype-udpate-frame-range-of-scene-doesn-t-update-frame-range-in-animation-instance commit 155a0c2 Author: Thomas Fricard Date: Thu Apr 13 10:57:53 2023 +0200 resolve conflicts commit 6a6c9e0 Author: Thomas Fricard Date: Wed Apr 12 14:07:19 2023 +0200 check if update instances is enabled in settings commit 9211c4a Author: Thomas Fricard Date: Wed Apr 12 13:58:40 2023 +0200 add a setting in maya project settings to enable updating animation instances commit fde0b50 Author: Thomas Fricard Date: Tue Apr 11 16:58:37 2023 +0200 check if attribute exsits before modifying it commit 38eed31 Author: Thomas Fricard Date: Wed Apr 5 15:26:04 2023 +0200 update animation instance frames attribute on reset frame range commit dc47ca9 Author: Thomas Fricard Date: Fri Mar 31 15:56:19 2023 +0200 get instances and set frameStart attribute commit 0932341 Author: Thomas Fricard Date: Wed May 17 12:47:17 2023 +0200 call reset_frame_range function for instances after importing commit 4de8e6e Author: Thomas Fricard Date: Mon May 15 17:12:13 2023 +0200 cosmetic refactoring commit efe2dbb Author: Thomas Fricard Date: Mon May 15 16:55:19 2023 +0200 check id attribute before family attribute commit 2379c5c Author: Thomas Fricard Date: Fri May 12 16:01:52 2023 +0200 verify that 'family' attributes exists and equals to 'render' commit 3a57274 Author: Thomas Fricard Date: Fri May 12 15:58:05 2023 +0200 change function name + docstrings commit 7f68109 Author: Thomas Fricard Date: Fri May 12 11:48:43 2023 +0200 add a 'type' key to the 'include handles' setting of maya commit 58fb563 Author: Thomas Fricard Date: Wed May 3 15:32:06 2023 +0200 merge and refactor functions to updte asset frame range commit 49401c7 Author: Thomas Fricard Date: Tue Apr 18 10:03:24 2023 +0200 add 'instances' to reset_frame_range function arguments and make sure it does not update frame range instances on creating render settings commit 8c0c60d Author: Thomas Fricard Date: Tue Apr 18 09:31:27 2023 +0200 change key and label to make the setting more understandable commit bdcdab2 Author: Thomas Fricard Date: Thu Apr 13 10:57:53 2023 +0200 resolve conflicts commit 9040302 Author: Thomas Fricard Date: Wed Apr 12 14:07:19 2023 +0200 check if update instances is enabled in settings commit cd884e8 Author: Thomas Fricard Date: Wed Apr 12 13:58:40 2023 +0200 add a setting in maya project settings to enable updating animation instances commit 981c0b1 Author: Thomas Fricard Date: Tue Apr 11 16:58:37 2023 +0200 check if attribute exsits before modifying it commit 1755ce4 Author: Thomas Fricard Date: Wed Apr 5 15:26:04 2023 +0200 update animation instance frames attribute on reset frame range commit fdf6f43 Author: Thomas Fricard Date: Fri Mar 31 15:56:19 2023 +0200 get instances and set frameStart attribute --- openpype/hosts/maya/api/lib.py | 88 ++++++++++++++----- openpype/hosts/maya/api/lib_rendersettings.py | 7 +- openpype/hosts/maya/api/pipeline.py | 3 +- .../maya/api/workfile_template_builder.py | 11 ++- .../defaults/project_settings/maya.json | 3 + .../projects_schema/schema_project_maya.json | 18 ++++ 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 8569bbd38fc..2353a59af92 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2260,7 +2260,7 @@ def get_frame_range(include_animation_range=False): return frame_range -def reset_frame_range(playback=True, render=True, fps=True): +def reset_frame_range(playback=True, render=True, fps=True, instances=True): """Set frame range to current asset Args: @@ -2269,6 +2269,8 @@ def reset_frame_range(playback=True, render=True, fps=True): render (bool, Optional): Whether to set the maya render frame range. Defaults to True. fps (bool, Optional): Whether to set scene FPS. Defaults to True. + instances (bool, Optional): Whether to update publishable instances. + Defaults to True. """ if fps: fps = convert_to_maya_fps( @@ -2299,6 +2301,12 @@ def reset_frame_range(playback=True, render=True, fps=True): cmds.setAttr("defaultRenderGlobals.startFrame", animation_start) cmds.setAttr("defaultRenderGlobals.endFrame", animation_end) + if instances: + project_name = get_current_project_name() + settings = get_project_settings(project_name) + if settings["maya"]["update_publishable_frame_range"]["enabled"]: + update_instances_frame_range() + def reset_scene_resolution(): """Apply the scene resolution from the project definition @@ -3131,31 +3139,63 @@ def remove_render_layer_observer(): pass -def update_content_on_context_change(): +def iter_publish_instances(): + """Iterate over publishable instances (their objectSets). """ - This will update scene content to match new asset on context change + for node in cmds.ls( + "*.id", + long=True, + type="objectSet", + recursive=True, + objectsOnly=True + ): + if cmds.getAttr("{}.id".format(node)) != "pyblish.avalon.instance": + continue + yield node + + +def update_instances_asset_name(): + """Update 'asset' attribute of publishable instances (their objectSets) + that got one. """ - scene_sets = cmds.listSets(allSets=True) - asset_doc = get_current_project_asset() - new_asset = asset_doc["name"] - new_data = asset_doc["data"] - for s in scene_sets: - try: - if cmds.getAttr("{}.id".format(s)) == "pyblish.avalon.instance": - attr = cmds.listAttr(s) - print(s) - if "asset" in attr: - print(" - setting asset to: [ {} ]".format(new_asset)) - cmds.setAttr("{}.asset".format(s), - new_asset, type="string") - if "frameStart" in attr: - cmds.setAttr("{}.frameStart".format(s), - new_data["frameStart"]) - if "frameEnd" in attr: - cmds.setAttr("{}.frameEnd".format(s), - new_data["frameEnd"],) - except ValueError: - pass + + for instance in iter_publish_instances(): + if not cmds.attributeQuery("asset", node=instance, exists=True): + continue + attr = "{}.asset".format(instance) + cmds.setAttr(attr, get_current_asset_name(), type="string") + + +def update_instances_frame_range(): + """Update 'frameStart', 'frameEnd', 'handleStart', 'handleEnd' and 'fps' + attributes of publishable instances (their objectSets) that got one. + """ + + attributes = ["frameStart", "frameEnd", "handleStart", "handleEnd", "fps"] + + attrs_per_instance = {} + for instance in iter_publish_instances(): + instance_attrs = [ + attr for attr in attributes + if cmds.attributeQuery(attr, node=instance, exists=True) + ] + + if instance_attrs: + attrs_per_instance[instance] = instance_attrs + + if not attrs_per_instance: + # no instances with any frame related attributes + return + + fields = ["data.{}".format(key) for key in attributes] + asset_doc = get_current_project_asset(fields=fields) + asset_data = asset_doc["data"] + + for node, attrs in attrs_per_instance.items(): + for attr in attrs: + plug = "{}.{}".format(node, attr) + value = asset_data[attr] + cmds.setAttr(plug, value) def show_message(title, msg): diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index eaa728a2f68..4deffcc46bf 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -158,7 +158,12 @@ def _set_arnold_settings(self, width, height): cmds.setAttr( "defaultArnoldDriver.mergeAOVs", multi_exr) self._additional_attribs_setter(additional_options) - reset_frame_range(playback=False, fps=False, render=True) + reset_frame_range( + playback=False, + fps=False, + render=True, + instances=False + ) def _set_redshift_settings(self, width, height): """Sets settings for Redshift.""" diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index e2d00b5bd7f..168e20c640b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -637,7 +637,8 @@ def on_task_changed(): with lib.suspended_refresh(): lib.set_context_settings() - lib.update_content_on_context_change() + lib.update_instances_frame_range() + lib.update_instances_asset_name() msg = " project: {}\n asset: {}\n task:{}".format( legacy_io.active_project(), diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 865f497710a..0eebeb91a4a 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -14,7 +14,14 @@ WorkfileBuildPlaceholderDialog, ) -from .lib import read, imprint, get_reference_node, get_main_window +from .lib import ( + read, + imprint, + get_reference_node, + get_main_window, + update_instances_frame_range, + update_instances_asset_name, +) PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -251,6 +258,8 @@ def post_placeholder_process(self, placeholder, failed): cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) + update_instances_frame_range() + update_instances_asset_name() def delete_placeholder(self, placeholder): """Remove placeholder if building was successful""" diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index e3fc5f07232..4fa1aca5526 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -457,6 +457,9 @@ "include_handles_default": false, "per_task_type": [] }, + "update_publishable_frame_range": { + "enabled": true + }, "scriptsmenu": { "name": "OpenPype Tools", "definition": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index dca955dab43..b6804533216 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -197,6 +197,24 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "update_publishable_frame_range", + "label": "Update publishable instances on Reset Frame Range", + "checkbox_key": "enabled", + "children": [ + { + "type": "label", + "label": "If enabled, the frame range and the handles of all the publishable instances will be updated when using the 'Reset Frame Range' functionality" + }, + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "dict", "key": "include_handles", From 2eb82f80a281f4afabc8578b76538f9ab11090fd Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 6 Jul 2023 14:30:17 +0200 Subject: [PATCH 34/84] Squashed commit of the following: commit f814db2ce0c4432023b18903c7819729997abfb5 Author: Toke Stuart Jepsen Date: Thu Jul 6 10:45:00 2023 +0100 Use colorspace data when creating thumbnail. --- openpype/lib/transcoding.py | 13 +++-- openpype/plugins/publish/extract_thumbnail.py | 51 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index de6495900e7..771f670f899 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1056,7 +1056,8 @@ def convert_colorspace( view=None, display=None, additional_command_args=None, - logger=None + logger=None, + input_args=None ): """Convert source file from one color space to another. @@ -1084,13 +1085,17 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - oiio_cmd = [ - get_oiio_tools_path(), + oiio_cmd = [get_oiio_tools_path()] + + if input_args: + oiio_cmd.extend(input_args) + + oiio_cmd.extend([ input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path - ] + ]) if all([target_colorspace, view, display]): raise ValueError("Colorspace and both screen and display" diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index b98ab64f560..1d86741470e 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -10,6 +10,7 @@ run_subprocess, path_to_subprocess_arg, ) +from openpype.lib.transcoding import convert_colorspace class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -98,8 +99,18 @@ def process(self, instance): self.log.debug("Trying to convert with OIIO") # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg + colorspace_data = repre["colorspaceData"] + source_colorspace = colorspace_data["colorspace"] + config_path = colorspace_data.get("config", {}).get("path") + display = colorspace_data["display"] + view = colorspace_data["view"] thumbnail_created = self.create_thumbnail_oiio( - full_input_path, full_output_path + full_input_path, + full_output_path, + config_path, + source_colorspace, + display, + view ) # Try to use FFMPEG if OIIO is not supported or for cases when @@ -172,24 +183,28 @@ def _get_filtered_repres(self, instance): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, src_path, dst_path): + def create_thumbnail_oiio( + self, + src_path, + dst_path, + config_path, + source_colorspace, + display, + view + ): self.log.info("Extracting thumbnail {}".format(dst_path)) - oiio_tool_path = get_oiio_tools_path() - oiio_cmd = [ - oiio_tool_path, - "-a", src_path, - "-o", dst_path - ] - self.log.debug("running: {}".format(" ".join(oiio_cmd))) - try: - run_subprocess(oiio_cmd, logger=self.log) - return True - except Exception: - self.log.warning( - "Failed to create thumbnail using oiiotool", - exc_info=True - ) - return False + + convert_colorspace( + src_path, + dst_path, + config_path, + source_colorspace, + view=view, + display=display, + input_args=["-i:ch=R,G,B"] + ) + + return dst_path def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From 37a78376a1108a2df51b36bc457e385d1108935c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 6 Jul 2023 15:45:08 +0200 Subject: [PATCH 35/84] bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 4a6131a26aa..23db0eb3f40 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-nightly.3" +__version__ = "3.15.12-quad.3.0" From 7f2fe9f05fce5cec3bbb7a29b3c23c7a9431cc20 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 13 Jul 2023 09:49:29 +0200 Subject: [PATCH 36/84] remove unique id for rig --- openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py index f7a5e6e2927..d21a38f0215 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -17,7 +17,6 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["model", "look", - "rig", "yetiRig"] actions = [openpype.hosts.maya.api.action.SelectInvalidAction, From d94909fc222e859f83de6a6aa9e84823e77212b8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 13 Jul 2023 09:49:58 +0200 Subject: [PATCH 37/84] bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 23db0eb3f40..eaeb423f424 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.0" +__version__ = "3.15.12-quad.3.1" From 6cf629e29d4835d2077db1392199307a32e16efd Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 17 Jul 2023 15:32:01 +0200 Subject: [PATCH 38/84] replace endswith by startswith in rig outputs id need this one for publish multiple rig in one asset. --- openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index cba70a21b73..5cd41d12762 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -46,7 +46,7 @@ def get_invalid_matches(cls, instance, compute=False): invalid = {} if compute: - out_set = next(x for x in instance if x.endswith("out_SET")) + out_set = next(x for x in instance if x.startswith("out_SET")) instance_nodes = cmds.sets(out_set, query=True, nodesOnly=True) instance_nodes = cmds.ls(instance_nodes, long=True) From 618f24596fa4561bf853413e81643dbc2a63983e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 17 Jul 2023 15:32:38 +0200 Subject: [PATCH 39/84] bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index eaeb423f424..ca6339bf6a8 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.1" +__version__ = "3.15.12-quad.3.2" From 046c2323c173d9ffcca93f58717591e6684983f8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Tue, 18 Jul 2023 14:40:25 +0200 Subject: [PATCH 40/84] add asset id from library --- .../publish/validate_node_ids_in_database.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py index b2f28fd4e5b..e95f8e0e0c0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -5,6 +5,7 @@ from openpype.pipeline.publish import ValidatePipelineOrder import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib +from openpype.client.entities import get_projects class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @@ -43,12 +44,16 @@ def get_invalid(cls, instance): nodes=instance[:]) # check ids against database ids - project_name = legacy_io.active_project() - asset_docs = get_assets(project_name, fields=["_id"]) - db_asset_ids = { - str(asset_doc["_id"]) - for asset_doc in asset_docs - } + projects_list = [legacy_io.active_project()] + for project in get_projects(fields=["name", "data.library_project"]): + if project.get("data", {}).get("library_project", False): + projects_list.append(project["name"]) + + db_asset_ids = set() + for project_name in projects_list: + asset_docs = get_assets(project_name, fields=["_id"]) + assets_ids = set( str(asset_doc["_id"]) for asset_doc in asset_docs ) + db_asset_ids.update(assets_ids) # Get all asset IDs for node in id_required_nodes: @@ -64,3 +69,12 @@ def get_invalid(cls, instance): invalid.append(node) return invalid + + def get_library_project_names(self): + libraries = list() + + for project in get_projects(fields=["name", "data.library_project"]): + if project.get("data", {}).get("library_project", False): + libraries.append(project["name"]) + + return libraries From d4bdb871345e72d9da53d923bc2f9e83d0d8e3d8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Tue, 18 Jul 2023 14:40:52 +0200 Subject: [PATCH 41/84] bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index ca6339bf6a8..898375330e1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.2" +__version__ = "3.15.12-quad.3.3" From 42d5a7ba401ebad40ccedffe1ad70fec9f2b0b7c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 4 Sep 2023 19:53:26 +0200 Subject: [PATCH 42/84] fix rolex attibut in look --- openpype/hosts/maya/plugins/publish/collect_look.py | 7 ++++++- openpype/version.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 287ddc228bb..5e7f105c160 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -554,7 +554,10 @@ def collect_attributes_changed(self, instance): self.log.warning("Attribute '{}' is mixed-type and is " "not supported yet.".format(attribute)) continue - if cmds.getAttr(attribute, type=True) == "message": + if cmds.getAttr(attribute, type=True) in [ + "message", + "TdataCompound" + ]: continue node_attributes[attr] = cmds.getAttr(attribute, asString=True) # Only include if there are any properties we care about @@ -589,6 +592,8 @@ def collect_resources(self, node): node, attribute )) + if not source: + continue computed_attribute = "{}.{}".format(node, attribute) if attribute == "fileTextureName": computed_attribute = node + ".computedFileTextureNamePattern" diff --git a/openpype/version.py b/openpype/version.py index 898375330e1..7a9f52b65e1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.3" +__version__ = "3.15.12-quad.3.4" From 9f136433895d8f8925ed0c5b856b2d3d23f84b09 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 4 Sep 2023 20:07:05 +0200 Subject: [PATCH 43/84] Squashed commit of the following: commit 5d98b3c400431d2e8019e4c283b31120d61b2865 Author: FadyFS Date: Thu Jul 6 10:00:20 2023 +0200 site config added --- openpype/tools/settings/local_settings/projects_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/local_settings/projects_widget.py b/openpype/tools/settings/local_settings/projects_widget.py index 4a4148d7cd7..670e63cfaca 100644 --- a/openpype/tools/settings/local_settings/projects_widget.py +++ b/openpype/tools/settings/local_settings/projects_widget.py @@ -285,7 +285,7 @@ def _get_sites_inputs(self): continue site_inputs = [] - site_config = site_configs[site_name] + site_config = site_configs.get(site_name, {}) for root_name, path_entity in site_config.get("root", {}).items(): if not path_entity: continue From dbb589b2ba6fd8b6b63062c746563e861454170e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 4 Sep 2023 20:07:47 +0200 Subject: [PATCH 44/84] bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 7a9f52b65e1..9dbe32c2326 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.4" +__version__ = "3.15.12-quad.3.5" From eb228c907608f87a802d28ce66e53f2a773b3532 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 19 Sep 2023 18:03:08 +0200 Subject: [PATCH 45/84] merge branch 558 bug export camera --- .../plugins/publish/extract_camera_alembic.py | 4 +++- .../publish/extract_camera_mayaScene.py | 18 +++++++----------- openpype/version.py | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index aa445a03879..3e08dd38783 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -85,7 +85,9 @@ def process(self, instance): transform = cmds.listRelatives( member, parent=True, fullPath=True) transform = transform[0] if transform else member - job_str += ' -root {0}'.format(transform) + + if transform not in camera_root: + job_str += ' -root {0}'.format(transform) job_str += ' -file "{0}"'.format(path) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 7467fa027d4..391ba7d44ee 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -106,12 +106,12 @@ def process(self, instance): instance.context.data["project_settings"]["maya"]["ext_mapping"] ) if ext_mapping: - self.log.info("Looking in settings for scene type ...") + self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found for family in self.families: try: self.scene_type = ext_mapping[family] - self.log.info( + self.log.debug( "Using {} as scene type".format(self.scene_type)) break except KeyError: @@ -133,8 +133,7 @@ def process(self, instance): # get cameras members = cmds.ls(instance.data['setMembers'], leaf=True, shapes=True, long=True, dag=True) - cameras = cmds.ls(members, leaf=True, shapes=True, long=True, - dag=True, type="camera") + cameras = cmds.ls(members, type="camera") # validate required settings assert isinstance(step, float), "Step must be a float value" @@ -151,7 +150,7 @@ def process(self, instance): with lib.evaluation("off"): with lib.suspended_refresh(): if bake_to_worldspace: - self.log.info( + self.log.debug( "Performing camera bakes: {}".format(transform)) baked = lib.bake_to_world_space( transform, @@ -163,9 +162,6 @@ def process(self, instance): dag=True, shapes=True, long=True) - - members = members + baked_camera_shapes - members.remove(camera) else: baked_camera_shapes = cmds.ls(cameras, type="camera", @@ -186,8 +182,8 @@ def process(self, instance): unlock(plug) cmds.setAttr(plug, value) - self.log.info("Performing extraction..") - cmds.select(cmds.ls(members, dag=True, + self.log.debug("Performing extraction..") + cmds.select(cmds.ls(baked, dag=True, shapes=True, long=True), noExpand=True) cmds.file(path, force=True, @@ -217,5 +213,5 @@ def process(self, instance): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '{0}' to: {1}".format( + self.log.debug("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/openpype/version.py b/openpype/version.py index 9dbe32c2326..8b9c5554863 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.5" +__version__ = "3.15.12-quad.3.8" From ee0e1facc43fd25228be7da5b8183d4c339ef4f4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 19 Sep 2023 18:04:46 +0200 Subject: [PATCH 46/84] resolve conflicts --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 603 ++++++++++++++++++ openpype/hosts/hiero/api/plugin.py | 5 +- .../hiero/plugins/create/create_shot_clip.py | 9 +- .../plugins/create/create_pointcache.py | 68 +- openpype/hosts/maya/api/lib.py | 10 +- openpype/hosts/maya/plugins/load/load_look.py | 22 +- openpype/hosts/nuke/api/lib.py | 57 +- .../nuke/plugins/load/load_camera_abc.py | 2 - openpype/hosts/nuke/plugins/load/load_clip.py | 7 +- .../hosts/nuke/plugins/load/load_effects.py | 7 +- .../nuke/plugins/load/load_effects_ip.py | 7 +- .../hosts/nuke/plugins/load/load_image.py | 7 +- .../hosts/nuke/plugins/load/load_model.py | 3 - .../nuke/plugins/load/load_script_precomp.py | 7 +- openpype/hosts/unreal/addon.py | 3 +- openpype/lib/__init__.py | 2 + openpype/lib/file_transaction.py | 9 +- openpype/lib/path_tools.py | 35 + openpype/pipeline/delivery.py | 2 + openpype/plugins/publish/integrate.py | 17 +- .../defaults/project_settings/hiero.json | 1 + .../projects_schema/schema_project_hiero.json | 5 + openpype/tools/sceneinventory/view.py | 101 +-- pyproject.toml | 2 +- 25 files changed, 869 insertions(+), 124 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9fcb69e2e98..1280e6a6e53 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.12-nightly.4 - 3.15.12-nightly.3 - 3.15.12-nightly.2 - 3.15.12-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.5-nightly.1 - 3.14.4 - 3.14.4-nightly.4 - - 3.14.4-nightly.3 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index 095e0d96e4c..b74fea7e3dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,609 @@ # Changelog +## [3.15.12](https://github.com/ynput/OpenPype/tree/3.15.12) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.11...3.15.12) + +### **🆕 New features** + + +
+Tray Publisher: User can set colorspace per instance explicitly #4901 + +With this feature a user can set/override the colorspace for the representations of an instance explicitly instead of relying on the File Rules from project settings or alike. This way you can ingest any file and explicitly say "this file is colorspace X". + + +___ + +
+ + +
+Review Family in Max #5001 + +Review Feature by creating preview animation in 3dsmax(The code is still cleaning up so there is going to be some updates until it is ready for review) + + +___ + +
+ + +
+AfterEffects: support for workfile template builder #5163 + +This PR add functionality of templated workfile builder. It allows someone to prepare AE workfile with placeholders as for automatically loading particular representation of particular subset of particular asset from context where workfile is opened.Selection from multiple prepared workfiles is provided with usage of templates, specific type of tasks could use particular workfile template etc.Artists then can build workfile from template when opening new workfile. + + +___ + +
+ + +
+CreatePlugin: Get next version helper #5242 + +Implemented helper functions to get next available versions for create instances. + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Improve Templates #4854 + +Use library method for fetching reference node and support parent in hierarchy. + + +___ + +
+ + +
+Bug: Maya - xgen sidecar files arent moved when saving workfile as an new asset workfile changing context - OP-6222 #5215 + +This PR manages the Xgen files when switching context in the Workfiles app. + + +___ + +
+ + +
+node references to check for duplicates in Max #5192 + +No duplicates for node references in Max when users trying to select nodes before publishing + + +___ + +
+ + +
+Tweak profiles logging to debug level #5194 + +Tweak profiles logging to debug level since they aren't artist facing logs. + + +___ + +
+ + +
+Enhancement: Reduce more visual clutter for artists in new publisher reports #5208 + +Got this from one of our artists' reports - figured some of these logs were definitely not for the artist, reduced those logs to debug level. + + +___ + +
+ + +
+Cosmetics: Tweak pyblish repair actions (icon, logs, docstring) #5213 + +- Add icon to RepairContextAction +- logs to debug level +- also add attempt repair for RepairAction for consistency +- fix RepairContextAction docstring to mention correct argument name + +#### Additional info + +We should not forget to remove this ["deprecated" actions.py file](https://github.com/ynput/OpenPype/blob/3501d0d23a78fbaef106da2fffe946cb49bef855/openpype/action.py) in 3.16 (next-minor) + +## Testing notes: + +1. Run some fabulous repairs! + +___ + +
+ + +
+Maya: fix save file prompt on launch last workfile with color management enabled + restructure `set_colorspace` #5225 + +- Only set `configFilePath` when OCIO env var is not set since it doesn't do anything if OCIO var is set anyway. +- Set the Maya 2022+ default OCIO path using the resources path instead of "" to avoid Maya Save File on new file after launch +- **Bugfix: This is what fixes the Save prompt on open last workfile feature with Global color management enabled** +- Move all code related to applying the maya settings together after querying the settings +- Swap around the `if use_workfile_settings` since the check was reversed +- Use `get_current_project_name()` instead of environment vars + + +___ + +
+ + +
+Enhancement: More descriptive error messages for Loaders #5227 + +Tweak raised errors and error messages for loader errors. + + +___ + +
+ + +
+Houdini: add select invalid action for ValidateSopOutputNode #5231 + +This PR adds `SelectROPAction` action to `houdini\api\action.py`and it's used in `Validate Output Node``SelectROPAction` is used to select the associated ROPs with the errored instances. + + +___ + +
+ + +
+Remove new lines from the delivery template string #5235 + +If the delivery template has a new line symbol at the end, say it was copied from the text editor, the delivery process will fail with `OSError` due to incorrect destination path. To avoid that I added `rstrip()` to the `delivery_path` processing. + + +___ + +
+ + +
+Houdini: better selection on pointcache creation #5250 + +Houdini allows `ObjNode` path as `sop_path` in the `ROP` unlike OP/ Ayon require `sop_path` to be set to a sop node path explicitly In this code, better selection is used to filter out invalid selections from OP/ Ayon point of viewValid selections are +- `SopNode` that has parent of type `geo` or `subnet` +- `ObjNode` of type `geo` that has +- `SopNode` of type `output` +- `SopNode` with render flag `on` (if no `Sopnode` of type `output`)this effectively filter +- empty `ObjNode` +- `ObjNode`(s) of other types like `cam` and `dopnet` +- `SopNode`(s) that thier parents of other types like `cam` and `sop solver` + + +___ + +
+ + +
+Update scene inventory even if any errors occurred during update #5252 + +When selecting many items in the scene inventory to update versions and one of the items would error out the updating stops. However, before this PR the scene inventory would also NOT refresh making you think it did nothing.Also implemented as method to allow some code deduplication. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Convert frame values to integers #5188 + +Convert frame values to integers. + + +___ + +
+ + +
+Maya: fix the register_event_callback correctly collecting workfile save after #5214 + +fixing the bug of register_event_callback not being able to collect action of "workfile_save_after" for lock file action + + +___ + +
+ + +
+Maya: aligning default settings to distributed aces 1.2 config #5233 + +Maya colorspace setttings defaults are set the way they align our distributed ACES 1.2 config file set in global colorspace configs. + + +___ + +
+ + +
+RepairAction and SelectInvalidAction filter instances failed on the exact plugin #5240 + +RepairAction and SelectInvalidAction actually filter to instances that failed on the exact plugin - not on "any failure" + + +___ + +
+ + +
+Maya: Bugfix look update nodes by id with non-unique shape names (query with `fullPath`) #5257 + +Fixes a bug where updating attributes on nodes with assigned shader if shape name existed more than once in the scene due to `cmds.listRelatives` call not being done with the `fullPath=True` flag.Original error: +```python +# Traceback (most recent call last): +# File "E:\openpype\OpenPype\openpype\tools\sceneinventory\view.py", line 264, in +# lambda: self._show_version_dialog(items)) +# File "E:\openpype\OpenPype\openpype\tools\sceneinventory\view.py", line 722, in _show_version_dialog +# self._update_containers(items, version) +# File "E:\openpype\OpenPype\openpype\tools\sceneinventory\view.py", line 849, in _update_containers +# update_container(item, item_version) +# File "E:\openpype\OpenPype\openpype\pipeline\load\utils.py", line 502, in update_container +# return loader.update(container, new_representation) +# File "E:\openpype\OpenPype\openpype\hosts\maya\plugins\load\load_look.py", line 119, in update +# nodes_by_id[lib.get_id(n)].append(n) +# File "E:\openpype\OpenPype\openpype\hosts\maya\api\lib.py", line 1420, in get_id +# sel.add(node) +``` + + +___ + +
+ + +
+Nuke: Create nodes with inpanel=False #5051 + +This PR is meant to remove the annoyance of the UI changing focus to the properties window just for the property window of the newly created node to disappear. Instead of using node.hideControlPanel I'm implementing the concealment during the creation of the node which will not change the focus of the current window. +___ + +
+ + +
+Fix the reset frame range not setting up the right timeline in Max #5187 + +Resolve #5181 + + +___ + +
+ + +
+Resolve: after launch automatization fixes #5193 + +Workfile is no correctly created and aligned witch actual project. Also the launching mechanism is now fixed so even no workfile had been saved yet it will open OpenPype menu automatically. + + +___ + +
+ + +
+General: Revert backward incompatible change of path to template to multiplatform #5197 + +Now platformity is still handed by usage of `work[root]` (or any other root that is accessible across platforms.) + + +___ + +
+ + +
+Nuke: root set format updating in node graph #5198 + +Nuke root node needs to be reset on some values so any knobs could be updated in node graph. This works the same way as an user would change frame number so expressions would update its values in knobs. + + +___ + +
+ + +
+Hiero: fixing otio current project and cosmetics #5200 + +Otio were not returning correct current project once additional Untitled project was open in project manager stack. + + +___ + +
+ + +
+Max: Publisher instances dont hold its enabled disabled states when Publisher reopened again #5202 + +Resolve #5183, general maxscript conversion issue to python (e.g. bool conversion, true in maxscript while True in Python)(Also resolve the ValueError when you change the subset to publish into list view menu) + + +___ + +
+ + +
+Burnins: Filter script is defined only for video streams #5205 + +Burnins are working for inputs with audio. + + +___ + +
+ + +
+Colorspace lib fix compatible python version comparison #5212 + +Fix python version comparison. + + +___ + +
+ + +
+Houdini: Fix `get_color_management_preferences` #5217 + +Fix the issue described here where the logic for retrieving the current OCIO display and view was incorrectly trying to apply a regex to it. + + +___ + +
+ + +
+Houdini: Redshift ROP image format bug #5218 + +Problem : +"RS_outputFileFormat" parm value was missing +and there were more "image_format" than redshift rop supports + +Fix: +1) removed unnecessary formats from `image_format_enum` +2) add the selected format value to `RS_outputFileFormat` +___ + +
+ + +
+Colorspace: check PyOpenColorIO rather then python version #5223 + +Fixing previously merged PR (https://github.com/ynput/OpenPype/pull/5212) And applying better way to check compatibility with PyOpenColorIO python api. + + +___ + +
+ + +
+Validate delivery action representations status #5228 + +- disable delivery button if no representations checked +- fix macos combobox layout +- add error message if no delivery templates found + + +___ + +
+ + +
+ Houdini: Add geometry check for pointcache family #5230 + +When `sop_path` on ABC ROP node points to a non `SopNode`, these validators `validate_abc_primitive_to_detail.py`, `validate_primitive_hierarchy_paths.py` will error and crash when this line is executed `geo = output_node.geometryAtFrame(frame)` + + +___ + +
+ + +
+Houdini: Add geometry check for VDB family #5232 + +When `sop_path` on Geometry ROP node points to a non SopNode, this validator `validate_vdb_output_node.py` will error and crash when this line is executed`sop_node.geometryAtFrame(frame)` + + +___ + +
+ + +
+Substance Painter: Include the setting only in publish tab #5234 + +Instead of having two settings in both create and publish tab, there is solely one setting in the publish tab for users to set up the parameters.Resolve #5172 + + +___ + +
+ + +
+Maya: Fix collecting arnold prefix when none #5243 + +When no prefix is specified in render settings, the renderlayer collector would error. + + +___ + +
+ + +
+Deadline: OPENPYPE_VERSION should only be added when running from build #5244 + +When running from source the environment variable `OPENPYPE_VERSION` should not be added. This is a bugfix for the feature #4489 + + +___ + +
+ + +
+Fix no prompt for "unsaved changes" showing when opening workfile in Houdini #5246 + +Fix no prompt for "unsaved changes" showing when opening workfile in Houdini. + + +___ + +
+ + +
+Fix no prompt for "unsaved changes" showing when opening workfile in Substance Painter #5248 + +Fix no prompt for "unsaved changes" showing when opening workfile in Substance Painter. + + +___ + +
+ + +
+General: add the os library before os.environ.get #5249 + +Adding os library into `creator_plugins.py` due to `os.environ.get` in line 667 + + +___ + +
+ + +
+Maya: Fix set_attribute for enum attributes #5261 + +Fix for #5260 + + +___ + +
+ + +
+Unreal: Move Qt imports away from module init #5268 + +Importing `Window` creates errors in headless mode. +``` +*** WRN: >>> { ModulesLoader }: [ FAILED to import host folder unreal ] +============================= +No Qt bindings could be found +============================= +Traceback (most recent call last): + File "C:\Users\tokejepsen\OpenPype\.venv\lib\site-packages\qtpy\__init__.py", line 252, in + from PySide6 import __version__ as PYSIDE_VERSION # analysis:ignore +ModuleNotFoundERROR: No module named 'PySide6' + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "C:\Users\tokejepsen\OpenPype\openpype\modules\base.py", line 385, in _load_modules + default_module = __import__( + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\unreal\__init__.py", line 1, in + from .addon import UnrealAddon + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\unreal\addon.py", line 4, in + from openpype.widgets.message_window import Window + File "C:\Users\tokejepsen\OpenPype\openpype\widgets\__init__.py", line 1, in + from .password_dialog import PasswordDialog + File "C:\Users\tokejepsen\OpenPype\openpype\widgets\password_dialog.py", line 1, in + from qtpy import QtWidgets, QtCore, QtGui + File "C:\Users\tokejepsen\OpenPype\.venv\lib\site-packages\qtpy\__init__.py", line 259, in + raise QtBindingsNotFoundERROR() +qtpy.QtBindingsNotFoundERROR: No Qt bindings could be found +``` + + +___ + +
+ +### **🔀 Refactored code** + + +
+Maya: Minor refactoring and code cleanup #5226 + +Some small cleanup and refactoring of logic. Removing old comments, unused imports and some minor optimization. Also removed the prints of the loader names of each container the scene in `fix_incompatible_containers` + optimizing by using `set` and defining only once. Moved some UI related code/tweaks to run `on_init` only if not in headless mode. Removed an empty `obj.py` file.Each commit message kind of describes why the change was made. + + +___ + +
+ +### **Merged pull requests** + + +
+Bug: Template builder fails when loading data without outliner representation #5222 + +I add an assertion management in case the container does not have a represention in outliner. + + +___ + +
+ + +
+AfterEffects - add container check validator to AE settings #5203 + +Adds check if scene contains only latest version of loaded containers. + + +___ + +
+ + + + ## [3.15.11](https://github.com/ynput/OpenPype/tree/3.15.11) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index a3f8a6c5243..e76c87f7a18 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -756,6 +756,7 @@ def convert(self): def _populate_track_item_default_data(self): """ Populate default formatting data from track item. """ + symlink = self.ui_inputs['hierarchyData']['value'].get('symlink') self.track_item_default_data = { "_folder_": "shots", @@ -763,7 +764,8 @@ def _populate_track_item_default_data(self): "_track_": self.track_name, "_clip_": self.ti_name, "_trackIndex_": self.track_index, - "_clipIndex_": self.ti_index + "_clipIndex_": self.ti_index, + "_symlink_": symlink["value"] } def _populate_attributes(self): @@ -787,6 +789,7 @@ def _populate_attributes(self): self.hierarchy_data = self.ui_inputs.get( "hierarchyData", {}).get("value") or \ self.track_item_default_data.copy() + self.hierarchy_data["symlink"].update({"value": "{_symlink_}"}) self.count_from = self.ui_inputs.get( "countFrom", {}).get("value") or self.count_from_default self.count_steps = self.ui_inputs.get( diff --git a/openpype/hosts/hiero/plugins/create/create_shot_clip.py b/openpype/hosts/hiero/plugins/create/create_shot_clip.py index d0c81cffa26..e8d78e3f7ae 100644 --- a/openpype/hosts/hiero/plugins/create/create_shot_clip.py +++ b/openpype/hosts/hiero/plugins/create/create_shot_clip.py @@ -102,7 +102,14 @@ class CreateShotClip(phiero.Creator): "label": "{shot}", "target": "tag", "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 4} + "order": 4}, + "symlink": { + "value": False, + "type": "QCheckBox", + "label": "Publish using symlink", + "target": "tag", + "toolTip": "Publish symlinks and not copied files", + "order": 5} } }, "verticalSync": { diff --git a/openpype/hosts/houdini/plugins/create/create_pointcache.py b/openpype/hosts/houdini/plugins/create/create_pointcache.py index df74070feef..554d5f2016b 100644 --- a/openpype/hosts/houdini/plugins/create/create_pointcache.py +++ b/openpype/hosts/houdini/plugins/create/create_pointcache.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating pointcache alembics.""" from openpype.hosts.houdini.api import plugin -from openpype.pipeline import CreatedInstance import hou @@ -14,15 +13,13 @@ class CreatePointCache(plugin.HoudiniCreator): icon = "gears" def create(self, subset_name, instance_data, pre_create_data): - import hou - instance_data.pop("active", None) instance_data.update({"node_type": "alembic"}) instance = super(CreatePointCache, self).create( subset_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) parms = { @@ -37,13 +34,44 @@ def create(self, subset_name, instance_data, pre_create_data): } if self.selected_nodes: - parms["sop_path"] = self.selected_nodes[0].path() + selected_node = self.selected_nodes[0] + + # Although Houdini allows ObjNode path on `sop_path` for the + # the ROP node we prefer it set to the SopNode path explicitly + + # Allow sop level paths (e.g. /obj/geo1/box1) + if isinstance(selected_node, hou.SopNode): + parms["sop_path"] = selected_node.path() + self.log.debug( + "Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'." + % selected_node.path() + ) + + # Allow object level paths to Geometry nodes (e.g. /obj/geo1) + # but do not allow other object level nodes types like cameras, etc. + elif isinstance(selected_node, hou.ObjNode) and \ + selected_node.type().name() in ["geo"]: + + # get the output node with the minimum + # 'outputidx' or the node with display flag + sop_path = self.get_obj_output(selected_node) + + if sop_path: + parms["sop_path"] = sop_path.path() + self.log.debug( + "Valid ObjNode selection, 'SOP Path' in ROP will be set to " + "the child path '%s'." + % sop_path.path() + ) - # try to find output node - for child in self.selected_nodes[0].children(): - if child.type().name() == "output": - parms["sop_path"] = child.path() - break + if not parms.get("sop_path", None): + self.log.debug( + "Selection isn't valid. 'SOP Path' in ROP will be empty." + ) + else: + self.log.debug( + "No Selection. 'SOP Path' in ROP will be empty." + ) instance_node.setParms(parms) instance_node.parm("trange").set(1) @@ -57,3 +85,23 @@ def get_network_categories(self): hou.ropNodeTypeCategory(), hou.sopNodeTypeCategory() ] + + def get_obj_output(self, obj_node): + """Find output node with the smallest 'outputidx'.""" + + outputs = obj_node.subnetOutputs() + + # if obj_node is empty + if not outputs: + return + + # if obj_node has one output child whether its + # sop output node or a node with the render flag + elif len(outputs) == 1: + return outputs[0] + + # if there are more than one, then it have multiple ouput nodes + # return the one with the minimum 'outputidx' + else: + return min(outputs, + key=lambda node: node.evalParm('outputidx')) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 2353a59af92..316f9cdea06 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1522,7 +1522,15 @@ def set_attribute(attribute, value, node): cmds.addAttr(node, longName=attribute, **kwargs) node_attr = "{}.{}".format(node, attribute) - if "dataType" in kwargs: + enum_type = cmds.attributeQuery(attribute, node=node, enum=True) + if enum_type and value_type == "str": + enum_string_values = cmds.attributeQuery( + attribute, node=node, listEnum=True + )[0].split(":") + cmds.setAttr( + "{}.{}".format(node, attribute), enum_string_values.index(value) + ) + elif "dataType" in kwargs: attr_type = kwargs["dataType"] cmds.setAttr(node_attr, value, type=attr_type) else: diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index 8f3e0176589..b060ae2b05c 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -29,7 +29,7 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): color = "orange" def process_reference(self, context, name, namespace, options): - import maya.cmds as cmds + from maya import cmds with lib.maintained_selection(): file_url = self.prepare_root_value(self.fname, @@ -113,8 +113,8 @@ def update(self, container, representation): # region compute lookup nodes_by_id = defaultdict(list) - for n in nodes: - nodes_by_id[lib.get_id(n)].append(n) + for node in nodes: + nodes_by_id[lib.get_id(node)].append(node) lib.apply_attributes(attributes, nodes_by_id) def _get_nodes_with_shader(self, shader_nodes): @@ -125,14 +125,16 @@ def _get_nodes_with_shader(self, shader_nodes): Returns node names """ - import maya.cmds as cmds + from maya import cmds - nodes_list = [] for shader in shader_nodes: - connections = cmds.listConnections(cmds.listHistory(shader, f=1), + future = cmds.listHistory(shader, future=True) + connections = cmds.listConnections(future, type='mesh') if connections: - for connection in connections: - nodes_list.extend(cmds.listRelatives(connection, - shapes=True)) - return nodes_list + # Ensure unique entries only to optimize query and results + connections = list(set(connections)) + return cmds.listRelatives(connections, + shapes=True, + fullPath=True) or [] + return [] diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 685ab3019d2..0efc46edafc 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -553,7 +553,9 @@ def add_write_node_legacy(name, **kwarg): w = nuke.createNode( "Write", - "name {}".format(name)) + "name {}".format(name), + inpanel=False + ) w["file"].setValue(kwarg["file"]) @@ -589,7 +591,9 @@ def add_write_node(name, file_path, knobs, **kwarg): w = nuke.createNode( "Write", - "name {}".format(name)) + "name {}".format(name), + inpanel=False + ) w["file"].setValue(file_path) @@ -1192,8 +1196,10 @@ def create_prenodes( # create node now_node = nuke.createNode( - nodeclass, "name {}".format(name)) - now_node.hideControlPanel() + nodeclass, + "name {}".format(name), + inpanel=False + ) # add for dependency linking for_dependency[name] = { @@ -1317,12 +1323,17 @@ def create_write_node( input_name = str(input.name()).replace(" ", "") # if connected input node was defined prev_node = nuke.createNode( - "Input", "name {}".format(input_name)) + "Input", + "name {}".format(input_name), + inpanel=False + ) else: # generic input node connected to nothing prev_node = nuke.createNode( - "Input", "name {}".format("rgba")) - prev_node.hideControlPanel() + "Input", + "name {}".format("rgba"), + inpanel=False + ) # creating pre-write nodes `prenodes` last_prenode = create_prenodes( @@ -1342,15 +1353,13 @@ def create_write_node( imageio_writes["knobs"], **data ) - write_node.hideControlPanel() # connect to previous node now_node.setInput(0, prev_node) # switch actual node to previous prev_node = now_node - now_node = nuke.createNode("Output", "name Output1") - now_node.hideControlPanel() + now_node = nuke.createNode("Output", "name Output1", inpanel=False) # connect to previous node now_node.setInput(0, prev_node) @@ -1517,8 +1526,10 @@ def create_write_node_legacy( else: # generic input node connected to nothing prev_node = nuke.createNode( - "Input", "name {}".format("rgba")) - prev_node.hideControlPanel() + "Input", + "name {}".format("rgba"), + inpanel=False + ) # creating pre-write nodes `prenodes` if prenodes: for node in prenodes: @@ -1530,8 +1541,10 @@ def create_write_node_legacy( # create node now_node = nuke.createNode( - klass, "name {}".format(pre_node_name)) - now_node.hideControlPanel() + klass, + "name {}".format(pre_node_name), + inpanel=False + ) # add data to knob for _knob in knobs: @@ -1561,14 +1574,18 @@ def create_write_node_legacy( if isinstance(dependent, (tuple or list)): for i, node_name in enumerate(dependent): input_node = nuke.createNode( - "Input", "name {}".format(node_name)) - input_node.hideControlPanel() + "Input", + "name {}".format(node_name), + inpanel=False + ) now_node.setInput(1, input_node) elif isinstance(dependent, str): input_node = nuke.createNode( - "Input", "name {}".format(node_name)) - input_node.hideControlPanel() + "Input", + "name {}".format(node_name), + inpanel=False + ) now_node.setInput(0, input_node) else: @@ -1583,15 +1600,13 @@ def create_write_node_legacy( "inside_{}".format(name), **_data ) - write_node.hideControlPanel() # connect to previous node now_node.setInput(0, prev_node) # switch actual node to previous prev_node = now_node - now_node = nuke.createNode("Output", "name Output1") - now_node.hideControlPanel() + now_node = nuke.createNode("Output", "name Output1", inpanel=False) # connect to previous node now_node.setInput(0, prev_node) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 11cc63d25c2..40822c9eb72 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -66,8 +66,6 @@ def load(self, context, name, namespace, data): object_name, file), inpanel=False ) - # hide property panel - camera_node.hideControlPanel() camera_node.forceValidate() camera_node["frame_rate"].setValue(float(fps)) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index cb3da79ef58..ee745825445 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -144,10 +144,9 @@ def load(self, context, name, namespace, options): # Create the Loader with the filename path set read_node = nuke.createNode( "Read", - "name {}".format(read_name)) - - # hide property panel - read_node.hideControlPanel() + "name {}".format(read_name), + inpanel=False + ) # to avoid multiple undo steps for rest of process # we will switch off undo-ing diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index d49f87a094a..eb1c905c4d4 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -88,10 +88,9 @@ def load(self, context, name, namespace, data): GN = nuke.createNode( "Group", - "name {}_1".format(object_name)) - - # hide property panel - GN.hideControlPanel() + "name {}_1".format(object_name), + inpanel=False + ) # adding content to the group node with GN: diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index bfe32c1ed9b..03be8654edd 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -89,10 +89,9 @@ def load(self, context, name, namespace, data): GN = nuke.createNode( "Group", - "name {}_1".format(object_name)) - - # hide property panel - GN.hideControlPanel() + "name {}_1".format(object_name), + inpanel=False + ) # adding content to the group node with GN: diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index f82ee4db88b..0a79ddada7e 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -119,10 +119,9 @@ def load(self, context, name, namespace, options): with viewer_update_and_undo_stop(): r = nuke.createNode( "Read", - "name {}".format(read_name)) - - # hide property panel - r.hideControlPanel() + "name {}".format(read_name), + inpanel=False + ) r["file"].setValue(file) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index f968da8475a..36781993eab 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -65,9 +65,6 @@ def load(self, context, name, namespace, data): inpanel=False ) - # hide property panel - model_node.hideControlPanel() - model_node.forceValidate() # Ensure all items are imported and selected. diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index 53e9a760031..b74fdf481a2 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -70,10 +70,9 @@ def load(self, context, name, namespace, data): # P = nuke.nodes.LiveGroup("file {}".format(file)) P = nuke.createNode( "Precomp", - "file {}".format(file)) - - # hide property panel - P.hideControlPanel() + "file {}".format(file), + inpanel=False + ) # Set colorspace defined in version data colorspace = context["version"]["data"].get("colorspace", None) diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index ed23950b35c..b5c978d98fb 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -1,7 +1,6 @@ import os import re from openpype.modules import IHostAddon, OpenPypeModule -from openpype.widgets.message_window import Window UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -21,6 +20,8 @@ def add_implementation_envs(self, env, app): from .lib import get_compatible_integration + from openpype.widgets.message_window import Window + pattern = re.compile(r'^\d+-\d+$') if not pattern.match(app.name): diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 06de486f2e1..3e8b1fea7f3 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -174,6 +174,7 @@ format_file_size, collect_frames, create_hard_link, + create_symlink, version_up, get_version_from_path, get_last_version_from_path, @@ -306,6 +307,7 @@ "format_file_size", "collect_frames", "create_hard_link", + "create_symlink", "version_up", "get_version_from_path", "get_last_version_from_path", diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index 80f4e81f2c3..b2f3d103ff1 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -4,7 +4,7 @@ import errno import six -from openpype.lib import create_hard_link +from openpype.lib import create_hard_link, create_symlink # this is needed until speedcopy for linux is fixed if sys.platform == "win32": @@ -53,6 +53,7 @@ class FileTransaction(object): MODE_COPY = 0 MODE_HARDLINK = 1 + MODE_SYMLINK = 2 def __init__(self, log=None, allow_queue_replacements=False): if log is None: @@ -78,7 +79,7 @@ def add(self, src, dst, mode=MODE_COPY): Args: src (str): Source path. dst (str): Destination path. - mode (MODE_COPY, MODE_HARDLINK): Transfer mode. + mode (MODE_COPY, MODE_HARDLINK, MODE_SYMLINK): Transfer mode. """ opts = {"mode": mode} @@ -143,6 +144,10 @@ def process(self): self.log.debug("Hardlinking file ... {} -> {}".format( src, dst)) create_hard_link(src, dst) + elif opts["mode"] == self.MODE_SYMLINK: + self.log.debug("Symlinking file ... {} -> {}".format( + src, dst)) + create_symlink(src, dst) self._transferred.append(dst) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 0b6d0a3391a..dcb61ef186b 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -112,6 +112,41 @@ def create_hard_link(src_path, dst_path): ) +def create_symlink(src_path, dst_path): + """Create symlink of file. + Args: + src_path(str): Full path to a file which is used as source for + symlink. + dst_path(str): Full path to a file where a link of source will be + added. + """ + # Use `os.symlink` if is available + # - should be for all platforms with newer python versions + if hasattr(os, "symlink"): + os.symlink(src_path, dst_path) + return + + # Windows implementation of symlinks ( + # - for older versions of python + if platform.system().lower() == "windows": + import ctypes + from ctypes.wintypes import BOOL + CreateSymLink = ctypes.windll.kernel32.CreateSymbolicLinkW + CreateSymLink.argtypes = [ + ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_void_p + ] + CreateSymLink.restype = BOOL + + res = CreateSymLink(dst_path, src_path, None) + if res == 0: + raise ctypes.WinError() + return + # Raises not implemented error if gets here + raise NotImplementedError( + "Implementation of symlink for current environment is missing." + ) + + def collect_frames(files): """Returns dict of source path and its frame, if from sequence diff --git a/openpype/pipeline/delivery.py b/openpype/pipeline/delivery.py index 500f54040ac..ddde45d4da5 100644 --- a/openpype/pipeline/delivery.py +++ b/openpype/pipeline/delivery.py @@ -157,6 +157,8 @@ def deliver_single_file( delivery_path = delivery_path.replace("..", ".") # Make sure path is valid for all platforms delivery_path = os.path.normpath(delivery_path.replace("\\", "/")) + # Remove newlines from the end of the string to avoid OSError during copy + delivery_path = delivery_path.rstrip() delivery_folder = os.path.dirname(delivery_path) if not os.path.exists(delivery_folder): diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f392cf67f70..6462666b62a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -301,6 +301,11 @@ def register(self, instance, file_transactions, filtered_repres): ) } + is_symlink_mode = False + hierarchy_data = instance.data.get("hierarchyData") + if hierarchy_data: + is_symlink_mode = hierarchy_data.get("symlink") + # Prepare all representations prepared_representations = [] for repre in filtered_repres: @@ -315,7 +320,14 @@ def register(self, instance, file_transactions, filtered_repres): for src, dst in prepared["transfers"]: # todo: add support for hardlink transfers - file_transactions.add(src, dst) + if is_symlink_mode: + file_transactions.add( + src, + dst, + mode=FileTransaction.MODE_SYMLINK + ) + else: + file_transactions.add(src, dst) prepared_representations.append(prepared) @@ -327,7 +339,8 @@ def register(self, instance, file_transactions, filtered_repres): file_copy_modes = [ ("transfers", FileTransaction.MODE_COPY), - ("hardlinks", FileTransaction.MODE_HARDLINK) + ("hardlinks", FileTransaction.MODE_HARDLINK), + ("symlinks", FileTransaction.MODE_SYMLINK) ] for files_type, copy_mode in file_copy_modes: for src, dst in instance.data.get(files_type, []): diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 9c83733b096..0c5048d19fd 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -41,6 +41,7 @@ "sequence": "sq01", "track": "{_track_}", "shot": "sh###", + "symlink": false, "vSyncOn": false, "workfileFrameStart": 1001, "handleStart": 10, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index d80edf902b2..259b42d94d6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -197,6 +197,11 @@ "type": "text", "key": "shot", "label": "{shot}" + }, + { + "type": "boolean", + "key": "symlink", + "label": "Publish using symlinks" } ] }, diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 73d33392b92..57e6e244111 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -1,5 +1,6 @@ import collections import logging +import itertools from functools import partial from qtpy import QtWidgets, QtCore @@ -195,20 +196,17 @@ def _on_switch_to_versioned(items): version_name_by_id[version_doc["_id"]] = \ version_doc["name"] + # Specify version per item to update to + update_items = [] + update_versions = [] for item in items: repre_id = item["representation"] version_id = version_id_by_repre_id.get(repre_id) version_name = version_name_by_id.get(version_id) if version_name is not None: - try: - update_container(item, version_name) - except AssertionError: - self._show_version_error_dialog( - version_name, [item] - ) - log.warning("Update failed", exc_info=True) - - self.data_changed.emit() + update_items.append(item) + update_versions.append(version_name) + self._update_containers(update_items, update_versions) update_icon = qtawesome.icon( "fa.asterisk", @@ -225,16 +223,6 @@ def _on_switch_to_versioned(items): update_to_latest_action = None if has_outdated or has_loaded_hero_versions: - # update to latest version - def _on_update_to_latest(items): - for item in items: - try: - update_container(item, -1) - except AssertionError: - self._show_version_error_dialog(None, [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() - update_icon = qtawesome.icon( "fa.angle-double-up", color=DEFAULT_COLOR @@ -245,21 +233,11 @@ def _on_update_to_latest(items): menu ) update_to_latest_action.triggered.connect( - lambda: _on_update_to_latest(items) + lambda: self._update_containers(items, version=-1) ) change_to_hero = None if has_available_hero_version: - # change to hero version - def _on_update_to_hero(items): - for item in items: - try: - update_container(item, HeroVersionType(-1)) - except AssertionError: - self._show_version_error_dialog('hero', [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() - # TODO change icon change_icon = qtawesome.icon( "fa.asterisk", @@ -271,7 +249,8 @@ def _on_update_to_hero(items): menu ) change_to_hero.triggered.connect( - lambda: _on_update_to_hero(items) + lambda: self._update_containers(items, + version=HeroVersionType(-1)) ) # set version @@ -740,14 +719,7 @@ def _show_version_dialog(self, items): if label: version = versions_by_label[label] - for item in items: - try: - update_container(item, version) - except AssertionError: - self._show_version_error_dialog(version, [item]) - log.warning("Update failed", exc_info=True) - # refresh model when done - self.data_changed.emit() + self._update_containers(items, version) def _show_switch_dialog(self, items): """Display Switch dialog""" @@ -782,9 +754,9 @@ def _show_version_error_dialog(self, version, items): Args: version: str or int or None """ - if not version: + if version == -1: version_str = "latest" - elif version == "hero": + elif isinstance(version, HeroVersionType): version_str = "hero" elif isinstance(version, int): version_str = "v{:03d}".format(version) @@ -841,10 +813,43 @@ def update_all(self): return # Trigger update to latest - for item in outdated_items: - try: - update_container(item, -1) - except AssertionError: - self._show_version_error_dialog(None, [item]) - log.warning("Update failed", exc_info=True) - self.data_changed.emit() + self._update_containers(outdated_items, version=-1) + + def _update_containers(self, items, version): + """Helper to update items to given version (or version per item) + + If at least one item is specified this will always try to refresh + the inventory even if errors occurred on any of the items. + + Arguments: + items (list): Items to update + version (int or list): Version to set to. + This can be a list specifying a version for each item. + Like `update_container` version -1 sets the latest version + and HeroTypeVersion instances set the hero version. + + """ + + if isinstance(version, (list, tuple)): + # We allow a unique version to be specified per item. In that case + # the length must match with the items + assert len(items) == len(version), ( + "Number of items mismatches number of versions: " + "{} items - {} versions".format(len(items), len(version)) + ) + versions = version + else: + # Repeat the same version infinitely + versions = itertools.repeat(version) + + # Trigger update to latest + try: + for item, item_version in zip(items, versions): + try: + update_container(item, item_version) + except AssertionError: + self._show_version_error_dialog(item_version, [item]) + log.warning("Update failed", exc_info=True) + finally: + # Always update the scene inventory view, even if errors occurred + self.data_changed.emit() diff --git a/pyproject.toml b/pyproject.toml index 06a74d91268..1d124574c73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.11" # OpenPype +version = "3.15.12" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f7de78b0e7e94e3ff50c8f3382bd287688610679 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 Oct 2023 17:58:13 +0200 Subject: [PATCH 47/84] increment version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 8b9c5554863..c7feceda8a9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.8" +__version__ = "3.15.12-quad.3.9" From 39b8e4d4ea82058ccd71418d3587ae3512791221 Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Tue, 10 Oct 2023 17:13:53 +0200 Subject: [PATCH 48/84] #60: feat: add 'workfile' tag to supported families for AE, PS and tvP to allow .psd imports --- openpype/hosts/aftereffects/plugins/load/load_file.py | 3 ++- openpype/hosts/photoshop/plugins/load/load_image.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py index 33a86aa505e..cf84a23739a 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_file.py +++ b/openpype/hosts/aftereffects/plugins/load/load_file.py @@ -17,7 +17,8 @@ class FileLoader(api.AfterEffectsLoader): "render", "prerender", "review", - "audio"] + "audio", + "review"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): diff --git a/openpype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py index 91a97877811..ebed1f11769 100644 --- a/openpype/hosts/photoshop/plugins/load/load_image.py +++ b/openpype/hosts/photoshop/plugins/load/load_image.py @@ -11,7 +11,7 @@ class ImageLoader(photoshop.PhotoshopLoader): Stores the imported asset in a container named after the asset. """ - families = ["image", "render"] + families = ["image", "render", "workfile"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): From 44aa9ededca6446212adb3ac7c388f9d9537865a Mon Sep 17 00:00:00 2001 From: Guilhemz <146825030+Guilhemz@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:24:49 +0200 Subject: [PATCH 49/84] #60: clean: update typo error --- openpype/hosts/aftereffects/plugins/load/load_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py index cf84a23739a..ea4eaaaf73f 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_file.py +++ b/openpype/hosts/aftereffects/plugins/load/load_file.py @@ -18,7 +18,7 @@ class FileLoader(api.AfterEffectsLoader): "prerender", "review", "audio", - "review"] + "workfile"] representations = ["*"] def load(self, context, name=None, namespace=None, data=None): From ee262da8dd86d3e6ac9f028850838d60a9cf01a1 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 11 Oct 2023 09:30:24 +0200 Subject: [PATCH 50/84] add a machine limit per job in the options of maya render set --- openpype/hosts/maya/plugins/create/create_render.py | 1 + openpype/hosts/maya/plugins/publish/collect_render.py | 4 ++++ .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 + 3 files changed, 6 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 46811758086..9dab90c635a 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -224,6 +224,7 @@ def _create_render_settings(self): self.data["priority"] = default_priority self.data["tile_priority"] = default_priority self.data["framesPerTask"] = 1 + self.data["machineLimit"] = 0 self.data["whitelist"] = False self.data["machineList"] = "" self.data["useMayaBatch"] = False diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index babd494758f..5fe814ccc72 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -314,6 +314,7 @@ def process(self, context): "tilesX": render_instance.data.get("tilesX") or 2, "tilesY": render_instance.data.get("tilesY") or 2, "priority": render_instance.data.get("priority"), + "machineLimit": render_instance.data.get("machineLimit") or 0, "convertToScanline": render_instance.data.get( "convertToScanline") or False, "useReferencedAovs": render_instance.data.get( @@ -399,6 +400,9 @@ def parse_options(self, render_globals): if pool_b: options["renderGlobals"].update({"SecondaryPool": pool_b}) + # Number of workers for the current job + options["machineLimit"] = attributes["machineLimit"] + # Machine list machine_list = attributes["machineList"] if machine_list: diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a6cdcb7e711..ecff58eed74 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -144,6 +144,7 @@ def get_job_info(self): job_info.SecondaryPool = instance.data.get("secondaryPool") job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) + job_info.MachineLimit = instance.data.get("machineLimit", 0) if self.group != "none" and self.group: job_info.Group = self.group From f6d2133134a1aa102c493a5671c8fe51751ea224 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 11 Oct 2023 09:55:25 +0200 Subject: [PATCH 51/84] get default value for machine limit from additional job info in deadline settings --- openpype/hosts/maya/plugins/create/create_render.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 9dab90c635a..bb15ecb3108 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -224,7 +224,12 @@ def _create_render_settings(self): self.data["priority"] = default_priority self.data["tile_priority"] = default_priority self.data["framesPerTask"] = 1 - self.data["machineLimit"] = 0 + self.data["machineLimit"] = self._project_settings.get( + "deadline").get( + "publish").get( + "MayaSubmitDeadline").get( + "jobInfo").get( + "machineLimit", 0) self.data["whitelist"] = False self.data["machineList"] = "" self.data["useMayaBatch"] = False From 1cd90cbd5e740c92e75012cdbae4130197996922 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 10:49:00 +0200 Subject: [PATCH 52/84] refacto: set default value correctly for .get() method --- openpype/hosts/maya/plugins/publish/collect_render.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 5fe814ccc72..90d64ef2dd4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -310,13 +310,13 @@ def process(self, context): "pixelAspect": lib.get_attr_in_layer( "defaultResolution.pixelAspect", layer=layer_name ), - "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 - "tilesX": render_instance.data.get("tilesX") or 2, - "tilesY": render_instance.data.get("tilesY") or 2, + "tileRendering": render_instance.data.get("tileRendering", False), # noqa: E501 + "tilesX": render_instance.data.get("tilesX", 2), + "tilesY": render_instance.data.get("tilesY", 2), "priority": render_instance.data.get("priority"), - "machineLimit": render_instance.data.get("machineLimit") or 0, + "machineLimit": render_instance.data.get("machineLimit", 0), "convertToScanline": render_instance.data.get( - "convertToScanline") or False, + "convertToScanline", False), "useReferencedAovs": render_instance.data.get( "useReferencedAovs") or render_instance.data.get( "vrayUseReferencedAovs") or False, From b04d7d9e18235d00e2bd36005601cd6d59e2a9d1 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 11:23:29 +0200 Subject: [PATCH 53/84] add a Group prefix for the batch name --- .../deadline/plugins/publish/submit_aftereffects_deadline.py | 2 +- .../deadline/plugins/publish/submit_celaction_deadline.py | 2 +- .../modules/deadline/plugins/publish/submit_fusion_deadline.py | 2 +- .../modules/deadline/plugins/publish/submit_harmony_deadline.py | 2 +- .../deadline/plugins/publish/submit_houdini_remote_publish.py | 2 +- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 2 +- .../modules/deadline/plugins/publish/submit_max_deadline.py | 2 +- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 2 +- .../plugins/publish/submit_maya_remote_publish_deadline.py | 2 +- .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index ec481ec81e7..d1d86ab903a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -57,7 +57,7 @@ def get_job_info(self): if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") dln_job_info.Name = self._instance.data["name"] - dln_job_info.BatchName = batch_name + dln_job_info.BatchName = "Group: " + batch_name dln_job_info.Plugin = "AfterEffects" dln_job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index 2f9bbfa6395..6a1414bb86b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -140,7 +140,7 @@ def payload_submit(self, "Plugin": "CelAction", # Top-level group name - "BatchName": batch_name, + "BatchName": "Group: " + batch_name, # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 868e953dc94..320458219fc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -151,7 +151,7 @@ def process(self, instance): payload = { "JobInfo": { # Top-level group name - "BatchName": batch_name, + "BatchName": "Group: " + batch_name, # Asset dependency to wait for at least the scene file to sync. "AssetDependency0": script_path, diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 6c5330b5f4a..a2f231e83ed 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -269,7 +269,7 @@ def get_job_info(self): batch_name = set_batch_name(self._instance, filename) if is_in_tests: batch_name += datetime.now().strftime("%d%m%Y%H%M%S") - job_info.BatchName = batch_name + job_info.BatchName = "Group: " + batch_name job_info.Department = self.department job_info.Group = self.group 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 68aa653804f..0ba7d8cea35 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py @@ -76,7 +76,7 @@ def process(self, context): "JobInfo": { "Plugin": "Houdini", "Pool": "houdini", # todo: remove hardcoded pool - "BatchName": batch_name, + "BatchName": "Group: " + batch_name, "Comment": context.data.get("comment", ""), "Priority": 50, "Frames": "1-1", # Always trigger a single frame 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 6f71ae55126..42d49971702 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -59,7 +59,7 @@ def get_job_info(self): batch_name = set_batch_name(instance, filename) job_info.Name = "{} - {}".format(filename, instance.name) - job_info.BatchName = batch_name + job_info.BatchName = "Group: " + batch_name job_info.Plugin = "Houdini" job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index b66e26eff5f..84da47d194f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -78,7 +78,7 @@ def get_job_info(self): batch_name = set_batch_name(instance, src_filename) job_info.Name = "%s - %s" % (src_filename, instance.name) - job_info.BatchName = batch_name + job_info.BatchName = "Group: " + batch_name job_info.Plugin = instance.data["plugin"] job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) job_info.EnableAutoTimeout = True diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index f6f220d3f07..2184786ae33 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -131,7 +131,7 @@ def get_job_info(self): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") job_info.Name = "%s - %s" % (src_filename, instance.name) - job_info.BatchName = batch_name + job_info.BatchName = "Group: " + batch_name job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) 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 4c87394f4d2..45e0a3db629 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 @@ -67,7 +67,7 @@ def process(self, instance): payload = { "JobInfo": { "Plugin": "MayaBatch", - "BatchName": batch_name, + "BatchName": "Group: " + batch_name, "Name": job_name, "UserName": instance.context.data["user"], "Comment": instance.context.data.get("comment", ""), diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 60b84d3cfe8..df1dec93b13 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -226,7 +226,7 @@ def payload_submit( payload = { "JobInfo": { # Top-level group name - "BatchName": batch_name, + "BatchName": "Group: " + batch_name, # Asset dependency to wait for at least the scene file to sync. # "AssetDependency0": script_path, From ee1209915e5566c67b4d600227eb2d6d32b0be0b Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 15:46:49 +0200 Subject: [PATCH 54/84] remove useless log --- 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 f1371fa6204..262153f0774 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -474,7 +474,6 @@ def get_render_attribute(attr, layer): ) def _get_checked_limit_groups(self, attributes): - self.log.debug(f"SELF METHODS: {dir(self)}") checked_limits = [] deadline_settings = get_system_settings()["modules"]["deadline"] From 9b868d982cac8ac52597fa01a90212b11fbcd8e5 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 16:30:44 +0200 Subject: [PATCH 55/84] convert get_deadline_limit_groups to a more generic method usable for different endpoints with different arguments --- openpype/modules/deadline/deadline_module.py | 23 ++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py index b73872b9ef4..1c5f807fba6 100644 --- a/openpype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -76,7 +76,7 @@ def get_deadline_pools(webservice, log=None): return response.json() @staticmethod - def get_deadline_limit_groups(webservice, log=None): + def get_deadline_data(webservice, endpoint, log=None, **kwargs): """Get Limits groups for Deadlin Args: webservice (str): Server url @@ -89,9 +89,23 @@ def get_deadline_limit_groups(webservice, log=None): if not log: log = Logger.get_logger(__name__) - argument = "{}/api/limitgroups?NamesOnly=true".format(webservice) + request = "{}/api/{}".format( + webservice, + endpoint + ) + + # Construct the full request with arguments + arguments = [] + for key, value in kwargs.items(): + new_argument = "{}={}".format(key, value) + arguments.append(new_argument) + + if arguments: + arguments = "&".join(arguments) + request = "{}?{}".format(request, arguments) + try: - response = requests_get(argument) + response = requests_get(request) except requests.exceptions.ConnectionError as exc: msg = "Cannot connect to DL web service {}".format(webservice) log.error(msg) @@ -100,8 +114,9 @@ def get_deadline_limit_groups(webservice, log=None): DeadlineWebserviceError("{} - {}".format(msg, exc)), sys.exc_info()[2] ) + print(f"RESPONSE: {response.json()}") if not response.ok: - log.warning("No limit group retrieved") + log.warning("The data requested could not be retrieved") return [] return response.json() From 308d1b7a5d76d0327079055f99dade8fb2ea1ebf Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 16:31:49 +0200 Subject: [PATCH 56/84] adapt method call to method refacto --- openpype/hosts/maya/plugins/create/create_render.py | 7 +++++-- openpype/hosts/maya/plugins/publish/collect_render.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 6e66003c7c9..0933d19d635 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -297,9 +297,12 @@ def _create_render_settings(self): self.data["secondaryPool"] = self._set_default_pool(pool_names, secondary_pool) - limit_groups = self.deadline_module.get_deadline_limit_groups( + requested_arguments = {"NamesOnly": True} + limit_groups = self.deadline_module.get_deadline_data( deadline_url, - self.log + "limitgroups", + log=self.log, + **requested_arguments ) self.data["limits"] = {"limits": limit_groups} diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 262153f0774..7dac1961648 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -504,9 +504,12 @@ def _get_checked_limit_groups(self, attributes): # get default deadline webservice url from deadline module deadline_servers = deadline_module.deadline_urls - limit_groups = deadline_module.get_deadline_limit_groups( + requested_arguments = {"NamesOnly": True} + limit_groups = deadline_module.get_deadline_data( deadline_settings['deadline_urls']["default"], - self.log + "limitgroups", + log=self.log, + **requested_arguments ) for group, value in zip(limit_groups, attributes['limits'][0]): if value is True: From cb89b9c87e993f875149322a1bc0a51a8d87dde7 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 Oct 2023 16:41:40 +0200 Subject: [PATCH 57/84] remove useless print --- openpype/modules/deadline/deadline_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py index 1c5f807fba6..e85e0cb0a9b 100644 --- a/openpype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -114,7 +114,6 @@ def get_deadline_data(webservice, endpoint, log=None, **kwargs): DeadlineWebserviceError("{} - {}".format(msg, exc)), sys.exc_info()[2] ) - print(f"RESPONSE: {response.json()}") if not response.ok: log.warning("The data requested could not be retrieved") return [] From 4878bd30f928500990665812589917a71595217d Mon Sep 17 00:00:00 2001 From: Benjamin Souchet Date: Thu, 12 Oct 2023 17:47:53 +0200 Subject: [PATCH 58/84] (#103) enhancement: fix doctring of get_deadline_data --- openpype/modules/deadline/deadline_module.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py index e85e0cb0a9b..824e9887d9e 100644 --- a/openpype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -77,12 +77,14 @@ def get_deadline_pools(webservice, log=None): @staticmethod def get_deadline_data(webservice, endpoint, log=None, **kwargs): - """Get Limits groups for Deadlin + """Get Limits groups for Deadline Args: webservice (str): Server url + endpoint (str): Request endpoint log (Logger) + kwargs (Any): Request payload content as key=value pairs Returns: - list: Limits + Any: Returns the json-encoded content of a response, if any. Throws: RuntimeError: If Deadline webservice is unreachable. """ From fc8b6bb38c1d9500b0cc858799274618ea2c4ec6 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 09:48:09 +0200 Subject: [PATCH 59/84] remove last character if its a _ --- openpype/modules/deadline/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index a30dace4dd7..7c8702b13a6 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -34,4 +34,7 @@ def set_batch_name(instance, filename): batch_name_list.pop(m.start()) batch_name = "".join(batch_name_list) + if batch_name.endswith("_"): + batch_name = batch_name[:-1] + return batch_name From 816995c856c4a0a6c4f2ff3bd1f67dfa4c48bebe Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 09:50:50 +0200 Subject: [PATCH 60/84] add a field in deadline project settings to set batch name and job name --- openpype/settings/defaults/project_settings/deadline.json | 1 + .../schemas/projects_schema/schema_project_deadline.json | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 058c8cf1a3d..fc8d2d7186e 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -1,5 +1,6 @@ { "deadline_servers": [], + "deadline_batch_name": "{asset}_{task[name]}_{version}_{subversion}.{ext}", "deadline_job_name": "{asset}_{task[name]}_{version}_{subversion}.{ext}", "publish": { "CollectDefaultDeadlineServer": { 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 471dae97375..1066e28d803 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -11,6 +11,11 @@ "label": "Deadline Webservice URLs", "multiselect": true }, + { + "type": "text", + "key": "deadline_batch_name", + "label": "Batch name" + }, { "type": "text", "key": "deadline_job_name", From 3a2358010f32135b5a3882cd59d3fabe60ce538e Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 12:30:14 +0200 Subject: [PATCH 61/84] rename function to set batch name to set_custom_deadline_name and make it adaptible for batch and job name --- openpype/modules/deadline/utils.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index 7c8702b13a6..0be18ee13b8 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -3,7 +3,7 @@ from openpype.settings import get_current_project_settings -def set_batch_name(instance, filename): +def set_custom_deadline_name(instance, filename, setting): context = instance.context basename, ext = os.path.splitext(filename) subversion = basename.split("_")[-1] @@ -26,15 +26,15 @@ def set_batch_name(instance, filename): "ext": ext[1:] } - batch_name_settings = get_current_project_settings()["deadline"]["deadline_job_name"] # noqa - batch_name = batch_name_settings.format(**formatting_data) + custom_name_settings = get_current_project_settings()["deadline"][setting] # noqa + custom_name = custom_name_settings.format(**formatting_data) - for m in re.finditer("__", batch_name): - batch_name_list = list(batch_name) - batch_name_list.pop(m.start()) - batch_name = "".join(batch_name_list) + for m in re.finditer("__", custom_name): + custom_name_list = list(custom_name) + custom_name_list.pop(m.start()) + custom_name = "".join(custom_name_list) - if batch_name.endswith("_"): - batch_name = batch_name[:-1] + if custom_name.endswith("_"): + custom_name = custom_name[:-1] - return batch_name + return custom_name From 829451123488bafa57549c106ebbd8fb88de66fa Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 14:51:10 +0200 Subject: [PATCH 62/84] add custom job_name and call right function --- .../publish/submit_aftereffects_deadline.py | 16 +++++++++++++--- .../plugins/publish/submit_celaction_deadline.py | 14 +++++++++++--- .../plugins/publish/submit_fusion_deadline.py | 15 ++++++++++++--- .../plugins/publish/submit_harmony_deadline.py | 16 +++++++++++++--- .../publish/submit_houdini_render_deadline.py | 15 ++++++++++++--- .../plugins/publish/submit_max_deadline.py | 15 ++++++++++++--- .../plugins/publish/submit_maya_deadline.py | 15 ++++++++++++--- .../submit_maya_remote_publish_deadline.py | 14 +++++++++++--- .../plugins/publish/submit_nuke_deadline.py | 16 ++++++++++++---- 9 files changed, 108 insertions(+), 28 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index d1d86ab903a..81d16fe1cce 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -11,7 +11,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.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -53,10 +53,20 @@ def get_job_info(self): context = self._instance.context filename = os.path.basename(self._instance.data["source"]) - batch_name = set_batch_name(self._instance, filename) + job_name = set_custom_deadline_name( + self._instance, + filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + self._instance, + filename, + "deadline_batch_name" + ) + if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") - dln_job_info.Name = self._instance.data["name"] + dln_job_info.Name = job_name dln_job_info.BatchName = "Group: " + batch_name dln_job_info.Plugin = "AfterEffects" dln_job_info.UserName = context.data.get( diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index 6a1414bb86b..a70aab2cc38 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -5,7 +5,7 @@ import requests import pyblish.api -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name class CelactionSubmitDeadline(pyblish.api.InstancePlugin): @@ -76,7 +76,11 @@ def payload_submit(self, render_path = os.path.normpath(render_path) script_name = os.path.basename(script_path) - batch_name = set_batch_name(instance, script_name) + batch_name = set_custom_deadline_name( + instance, + script_name, + "deadline_batch_name" + ) for item in instance.context: if "workfile" in item.data["family"]: @@ -97,7 +101,11 @@ def payload_submit(self, "Using published scene for render {}".format(script_path) ) - jobname = "%s - %s" % (script_name, instance.name) + jobname = set_custom_deadline_name( + instance, + script_name, + "deadline_job_name" + ) output_filename_0 = self.preview_fname(render_path) diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 320458219fc..e4055e394b8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -14,7 +14,7 @@ BoolDef, NumberDef ) -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name class FusionSubmitDeadline( @@ -142,7 +142,16 @@ def process(self, instance): ) filename = os.path.basename(script_path) - batch_name = set_batch_name(instance, filename) + job_name = set_custom_deadline_name( + instance, + filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + filename, + "deadline_batch_name" + ) # Documentation for keys available at: # https://docs.thinkboxsoftware.com @@ -157,7 +166,7 @@ def process(self, instance): "AssetDependency0": script_path, # Job name, as seen in Monitor - "Name": filename, + "Name": job_name, "Priority": attribute_values.get( "priority", self.priority), diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index a2f231e83ed..1b4a6f17871 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -13,7 +13,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.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -253,7 +253,6 @@ class HarmonySubmitDeadline( def get_job_info(self): job_info = DeadlineJobInfo("Harmony") - job_info.Name = self._instance.data["name"] job_info.Plugin = "HarmonyOpenPype" job_info.Frames = "{}-{}".format( self._instance.data["frameStartHandle"], @@ -266,10 +265,21 @@ def get_job_info(self): job_info.SecondaryPool = self._instance.data.get("secondaryPool") job_info.ChunkSize = self.chunk_size filename = os.path.basename(self._instance.data["source"]) - batch_name = set_batch_name(self._instance, filename) + job_name = set_custom_deadline_name( + self._instance, + filename, + "deadline_job_name" + ) + + batch_name = set_custom_deadline_name( + self._instance, + filename, + "deadline_batch_name" + ) if is_in_tests: batch_name += datetime.now().strftime("%d%m%Y%H%M%S") job_info.BatchName = "Group: " + batch_name + job_info.Name = job_name job_info.Department = self.department job_info.Group = self.group 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 42d49971702..d2b6aaeedd9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -10,7 +10,7 @@ from openpype.tests.lib import is_in_tests from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name from openpype.lib import is_running_from_build @@ -56,9 +56,18 @@ def get_job_info(self): filepath = context.data["currentFile"] filename = os.path.basename(filepath) - batch_name = set_batch_name(instance, filename) + job_name = set_custom_deadline_name( + instance, + filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + filename, + "deadline_batch_name" + ) - job_info.Name = "{} - {}".format(filename, instance.name) + job_info.Name = job_name job_info.BatchName = "Group: " + batch_name job_info.Plugin = "Houdini" job_info.UserName = context.data.get( diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 84da47d194f..ac42a4cf652 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -20,7 +20,7 @@ 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 -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name @attr.s @@ -75,9 +75,18 @@ def get_job_info(self): src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) - batch_name = set_batch_name(instance, src_filename) + job_name = set_custom_deadline_name( + instance, + src_filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + src_filename, + "deadline_batch_name" + ) - job_info.Name = "%s - %s" % (src_filename, instance.name) + job_info.Name = job_name job_info.BatchName = "Group: " + batch_name job_info.Plugin = instance.data["plugin"] job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 2184786ae33..c43ee6b3a28 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -37,7 +37,7 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build @@ -125,12 +125,21 @@ def get_job_info(self): src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) - batch_name = set_batch_name(instance, src_filename) + job_name = set_custom_deadline_name( + instance, + src_filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + src_filename, + "deadline_batch_name" + ) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") - job_info.Name = "%s - %s" % (src_filename, instance.name) + job_info.Name = job_name job_info.BatchName = "Group: " + batch_name job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) 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 45e0a3db629..ab9a597ea97 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 @@ -8,7 +8,7 @@ from openpype.settings import get_project_settings from openpype.tests.lib import is_in_tests from openpype.lib import is_running_from_build -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name import pyblish.api @@ -58,8 +58,16 @@ def process(self, instance): scene = instance.context.data["currentFile"] scenename = os.path.basename(scene) - job_name = "{scene} [PUBLISH]".format(scene=scenename) - batch_name = set_batch_name(instance, scenename) + job_name = set_custom_deadline_name( + instance, + scenename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + scenename, + "deadline_batch_name" + ) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index df1dec93b13..e0e4e77ca40 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -18,7 +18,7 @@ BoolDef, NumberDef ) -from openpype.modules.deadline.utils import set_batch_name +from openpype.modules.deadline.utils import set_custom_deadline_name class NukeSubmitDeadline(pyblish.api.InstancePlugin, @@ -202,9 +202,17 @@ def payload_submit( ): render_dir = os.path.normpath(os.path.dirname(render_path)) filename = os.path.basename(script_path) - jobname = "%s - %s" % (filename, instance.name) - batch_name = set_batch_name(instance, filename) + job_name = set_custom_deadline_name( + instance, + filename, + "deadline_job_name" + ) + batch_name = set_custom_deadline_name( + instance, + filename, + "deadline_batch_name" + ) if is_in_tests(): batch_name += datetime.now().strftime("%d%m%Y%H%M%S") @@ -232,7 +240,7 @@ def payload_submit( # "AssetDependency0": script_path, # Job name, as seen in Monitor - "Name": jobname, + "Name": job_name, # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, From d0ac4f759e54dae47d296be4126bb46f57250aeb Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 16:37:44 +0200 Subject: [PATCH 63/84] add instance to usable variables for custom batch and job name --- openpype/modules/deadline/utils.py | 1 + openpype/settings/defaults/project_settings/deadline.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index 0be18ee13b8..d20cdb8951a 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -23,6 +23,7 @@ def set_custom_deadline_name(instance, filename, setting): "family": instance.data.get("family"), "comment": instance.data.get("comment"), "subversion": subversion, + "inst_name": instance.data.get("name"), "ext": ext[1:] } diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index fc8d2d7186e..586ebf0ccdf 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -1,7 +1,7 @@ { "deadline_servers": [], "deadline_batch_name": "{asset}_{task[name]}_{version}_{subversion}.{ext}", - "deadline_job_name": "{asset}_{task[name]}_{version}_{subversion}.{ext}", + "deadline_job_name": "{asset}_{task[name]}_{version}_{subversion}.{ext} - {inst_name}", "publish": { "CollectDefaultDeadlineServer": { "pass_mongo_url": true From ee1ca5e217648c0bf571cac0afee11246e1a66bf Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 17:07:41 +0200 Subject: [PATCH 64/84] add SafeDict class to handle missing keys --- openpype/modules/deadline/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index d20cdb8951a..657698f3375 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -1,8 +1,14 @@ import os import re + from openpype.settings import get_current_project_settings +class SafeDict(dict): + def __missing__(self, key): + return '{' + key + '}' + + def set_custom_deadline_name(instance, filename, setting): context = instance.context basename, ext = os.path.splitext(filename) @@ -28,7 +34,7 @@ def set_custom_deadline_name(instance, filename, setting): } custom_name_settings = get_current_project_settings()["deadline"][setting] # noqa - custom_name = custom_name_settings.format(**formatting_data) + custom_name = custom_name_settings.format_map(SafeDict(**formatting_data)) for m in re.finditer("__", custom_name): custom_name_list = list(custom_name) From f2ae92838bd8484f0a9bfb41cfa58063f065ea76 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 16 Oct 2023 17:32:11 +0200 Subject: [PATCH 65/84] add a try except for the batch and job name in case of typo in the settings template --- openpype/modules/deadline/utils.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/utils.py b/openpype/modules/deadline/utils.py index 657698f3375..ee1720603fa 100644 --- a/openpype/modules/deadline/utils.py +++ b/openpype/modules/deadline/utils.py @@ -34,14 +34,23 @@ def set_custom_deadline_name(instance, filename, setting): } custom_name_settings = get_current_project_settings()["deadline"][setting] # noqa - custom_name = custom_name_settings.format_map(SafeDict(**formatting_data)) - - for m in re.finditer("__", custom_name): - custom_name_list = list(custom_name) - custom_name_list.pop(m.start()) - custom_name = "".join(custom_name_list) - - if custom_name.endswith("_"): - custom_name = custom_name[:-1] + try: + custom_name = custom_name_settings.format_map( + SafeDict(**formatting_data) + ) + + for m in re.finditer("__", custom_name): + custom_name_list = list(custom_name) + custom_name_list.pop(m.start()) + custom_name = "".join(custom_name_list) + + if custom_name.endswith("_"): + custom_name = custom_name[:-1] + except Exception as e: + raise KeyError( + "OpenPype Studio Settings (Deadline section): Syntax issue(s) " + "in \"Job Name\" or \"Batch Name\" for the current project. " + "Error: {}".format(e) + ) return custom_name From 09da64d65f19bf74302b645c0b4772cf41076534 Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Tue, 17 Oct 2023 12:29:18 +0200 Subject: [PATCH 66/84] #152: feat: add auto created playblast subset for tvPaint export --- .../publish/collect_render_instances.py | 17 ++++++----- .../plugins/publish/extract_sequence.py | 30 +++++++++++++++---- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index e89fbf78829..2dcfe089143 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -20,7 +20,7 @@ def process(self, instance): elif creator_identifier == "render.pass": self._collect_data_for_render_pass(instance) - elif creator_identifier == "render.scene": + elif creator_identifier in ["render.scene", "render.playblast"]: self._collect_data_for_render_scene(instance) else: @@ -100,13 +100,14 @@ def _collect_data_for_render_scene(self, instance): 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}) - ) + if instance.data["creator_attributes"].get('render_pass_name'): + 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( diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 8a610cf3881..8b2b90db313 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -31,6 +31,11 @@ class ExtractSequence(pyblish.api.Extractor): review_bg = [255, 255, 255, 255] def process(self, instance): + print('###\n####\n###') + print(ExtractSequence) + print('###\n####\n###') + self.log.debug(instance.data['family']) + self.log.debug(instance.data['families']) self.log.info( "* Processing instance \"{}\"".format(instance.data["label"]) ) @@ -62,7 +67,9 @@ def process(self, instance): ignore_layers_transparency = instance.data.get( "ignoreLayersTransparency", False ) + print(instance.context.data) + print(instance.data) family_lowered = instance.data["family"].lower() mark_in = instance.context.data["sceneMarkIn"] mark_out = instance.context.data["sceneMarkOut"] @@ -111,9 +118,16 @@ def process(self, instance): "Files will be rendered to folder: {}".format(output_dir) ) - if instance.data["family"] == "review": + export_type = instance.data["creator_attributes"].get("export_type", "project") + print('####\n###\n### EXPORT TYPE') + print(export_type) + print('####\n###\n###') + + is_review = instance.data["family"] == "review" + is_playblast = instance.data["creator_identifier"] == "render.playblast" + if is_review or is_playblast: result = self.render_review( - output_dir, mark_in, mark_out, scene_bg_color + output_dir, export_type, mark_in, mark_out, scene_bg_color ) else: # Render output @@ -204,7 +218,7 @@ def _rename_output_files( return repre_filenames def render_review( - self, output_dir, mark_in, mark_out, scene_bg_color + self, output_dir, export_type, mark_in, mark_out, scene_bg_color ): """ Export images from TVPaint using `tv_savesequence` command. @@ -236,12 +250,16 @@ def render_review( "export_path = \"{}\"".format( first_frame_filepath.replace("\\", "/") ), - "tv_savesequence '\"'export_path'\"' {} {}".format( - mark_in, mark_out + "tv_projectsavesequence '\"'export_path'\"' \"{}\" {} {}".format( + export_type, mark_in, mark_out ) ] + print('####\n###\n### CMD') + print(george_script_lines) + print('####\n###\n###') + if scene_bg_color: - # Change bg color back to previous scene bg color + # Change bg color back to previous scene bg colorq _scene_bg_color = copy.deepcopy(scene_bg_color) bg_type = _scene_bg_color.pop(0) orig_color_command = [ From abb8b16d6ac7ab4e01269803374f1c3b70b8378f Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Tue, 17 Oct 2023 16:03:25 +0200 Subject: [PATCH 67/84] #152: clean: remove unused prints --- .../tvpaint/plugins/publish/extract_sequence.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 8b2b90db313..cc07bc44637 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -31,11 +31,6 @@ class ExtractSequence(pyblish.api.Extractor): review_bg = [255, 255, 255, 255] def process(self, instance): - print('###\n####\n###') - print(ExtractSequence) - print('###\n####\n###') - self.log.debug(instance.data['family']) - self.log.debug(instance.data['families']) self.log.info( "* Processing instance \"{}\"".format(instance.data["label"]) ) @@ -67,9 +62,7 @@ def process(self, instance): ignore_layers_transparency = instance.data.get( "ignoreLayersTransparency", False ) - print(instance.context.data) - print(instance.data) family_lowered = instance.data["family"].lower() mark_in = instance.context.data["sceneMarkIn"] mark_out = instance.context.data["sceneMarkOut"] @@ -119,9 +112,6 @@ def process(self, instance): ) export_type = instance.data["creator_attributes"].get("export_type", "project") - print('####\n###\n### EXPORT TYPE') - print(export_type) - print('####\n###\n###') is_review = instance.data["family"] == "review" is_playblast = instance.data["creator_identifier"] == "render.playblast" @@ -254,9 +244,6 @@ def render_review( export_type, mark_in, mark_out ) ] - print('####\n###\n### CMD') - print(george_script_lines) - print('####\n###\n###') if scene_bg_color: # Change bg color back to previous scene bg colorq From baad7e791dc0ed2c11ea6f6ce3da0198c2da46b0 Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Tue, 17 Oct 2023 17:21:02 +0200 Subject: [PATCH 68/84] Bump version to 3.15.12-quad.3.10 --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c7feceda8a9..6b5cb94ec46 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.9" +__version__ = "3.15.12-quad.3.10" From 58c57dc4b403e81e6769e1e7f283c0a61ac820dc Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Thu, 19 Oct 2023 18:47:18 +0200 Subject: [PATCH 69/84] add try excepte for oiio command --- openpype/plugins/publish/extract_thumbnail.py | 22 ++++++++++++------- .../publish/extract_thumbnail_from_source.py | 12 +++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 1d86741470e..1529b6651ae 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -104,14 +104,20 @@ def process(self, instance): config_path = colorspace_data.get("config", {}).get("path") display = colorspace_data["display"] view = colorspace_data["view"] - thumbnail_created = self.create_thumbnail_oiio( - full_input_path, - full_output_path, - config_path, - source_colorspace, - display, - view - ) + try: + thumbnail_created = self.create_thumbnail_oiio( + full_input_path, + full_output_path, + config_path, + source_colorspace, + display, + view + ) + except Exception as e: + self.log.debug(( + "Converting with OIIO failed " + "with the following error {}".format(e) + )) # Try to use FFMPEG if OIIO is not supported or for cases when # oiiotool isn't available diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py index a9c95d60652..326cee3f2ff 100644 --- a/openpype/plugins/publish/extract_thumbnail_from_source.py +++ b/openpype/plugins/publish/extract_thumbnail_from_source.py @@ -107,9 +107,15 @@ def _create_thumbnail(self, context, thumbnail_source): self.log.debug("Trying to convert with OIIO") # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - thumbnail_created = self.create_thumbnail_oiio( - thumbnail_source, full_output_path - ) + try: + thumbnail_created = self.create_thumbnail_oiio( + thumbnail_source, full_output_path + ) + except Exception as e: + self.log.debug(( + "Converting with OIIO failed " + "with the following error {}".format(e) + )) # Try to use FFMPEG if OIIO is not supported or for cases when # oiiotool isn't available From 623d7655cbc7bb1cea54528ff9582386b8276ab5 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Fri, 20 Oct 2023 16:56:34 +0200 Subject: [PATCH 70/84] add input args to limit input channels and speed up transcode process --- openpype/plugins/publish/extract_color_transcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index f7c8af93182..672b68517be 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -158,7 +158,8 @@ def process(self, instance): view, display, additional_command_args, - self.log + self.log, + input_args=["-i:ch=R,G,B"] ) # cleanup temporary transcoded files From f44f6485f3a72cfb7a780607c08d1eb515d1d591 Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Mon, 23 Oct 2023 16:42:58 +0200 Subject: [PATCH 71/84] #159: feat: update family check in integrate_kitsu_note (from review to render) and add condition in extract_sequence to redirect render --- .../publish/collect_render_instances.py | 17 +++++++++++++++- .../plugins/publish/extract_sequence.py | 20 ++++++++++++++----- .../plugins/publish/integrate_kitsu_note.py | 4 +++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index e89fbf78829..8974c7d7b33 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -24,7 +24,7 @@ def process(self, instance): self._collect_data_for_render_scene(instance) else: - if creator_identifier == "scene.review": + if creator_identifier in ["scene.review", "publish.sequence"]: self._collect_data_for_review(instance) return @@ -112,3 +112,18 @@ def _collect_data_for_review(self, instance): instance.data["layers"] = copy.deepcopy( instance.context.data["layersData"] ) + + transparency_from_creator = instance.data["creator_attributes"].get( + "ignore_layers_transparency", None) + if transparency_from_creator: + instance.data["ignoreLayersTransparency"] = ( + transparency_from_creator + ) + else: + instance.data["ignoreLayersTransparency"] = ( + self.ignore_render_pass_transparency + ) + + print('###\n###\n###') + print('IGNORE LAYERS TRANSPARENCY') + print(instance.data["ignoreLayersTransparency"]) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 8a610cf3881..f982a628e46 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -111,9 +111,16 @@ def process(self, instance): "Files will be rendered to folder: {}".format(output_dir) ) - if instance.data["family"] == "review": + export_type = instance.data["creator_attributes"].get("export_type", "project") + is_review = instance.data["family"] == "review" + publish_sequence_with_transparency = ( + instance.data["creator_identifier"] == "publish.sequence" and \ + not instance.data["creator_attributes"].get('ignore_layers_transparency', False) + ) + + if is_review or publish_sequence_with_transparency: result = self.render_review( - output_dir, mark_in, mark_out, scene_bg_color + output_dir, export_type, mark_in, mark_out, scene_bg_color ) else: # Render output @@ -146,6 +153,8 @@ def process(self, instance): tags = [] if "review" in instance.data["families"]: tags.append("review") + else: + tags.append("sequence") # Sequence of one frame single_file = len(repre_files) == 1 @@ -204,7 +213,7 @@ def _rename_output_files( return repre_filenames def render_review( - self, output_dir, mark_in, mark_out, scene_bg_color + self, output_dir, export_type, mark_in, mark_out, scene_bg_color ): """ Export images from TVPaint using `tv_savesequence` command. @@ -236,10 +245,11 @@ def render_review( "export_path = \"{}\"".format( first_frame_filepath.replace("\\", "/") ), - "tv_savesequence '\"'export_path'\"' {} {}".format( - mark_in, mark_out + "tv_projectsavesequence '\"'export_path'\"' \"{}\" {} {}".format( + export_type, mark_in, mark_out ) ] + if scene_bg_color: # Change bg color back to previous scene bg color _scene_bg_color = copy.deepcopy(scene_bg_color) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 6e5dd056f3c..874ca382e0c 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -55,7 +55,9 @@ def process(self, context): families = set( [instance.data["family"]] + instance.data.get("families", []) ) - if "review" not in families: + print("\n####\n###") + print(families) + if "render" not in families: continue kitsu_task = instance.data.get("kitsu_task") From 26bbfde8d388d58e44900cc8a9a1b6066916c2f3 Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Wed, 25 Oct 2023 10:02:57 +0200 Subject: [PATCH 72/84] Bump version to 3.15.12-quad.3.11 --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 6b5cb94ec46..3abfa89cbb5 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.10" +__version__ = "3.15.12-quad.3.11" From e956a010dfeea416aa52d808ced3ad9ba3718b57 Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Wed, 25 Oct 2023 12:00:55 +0200 Subject: [PATCH 73/84] fix* extract thumbnail crash on missing ocio dict keys --- openpype/plugins/publish/extract_thumbnail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 1529b6651ae..d10e9480a4b 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -102,8 +102,8 @@ def process(self, instance): colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("config", {}).get("path") - display = colorspace_data["display"] - view = colorspace_data["view"] + display = colorspace_data.get("display") + view = colorspace_data.get("view") try: thumbnail_created = self.create_thumbnail_oiio( full_input_path, From 324b82852881838dbcab23fc08e3d0905110d016 Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Wed, 25 Oct 2023 18:30:10 +0200 Subject: [PATCH 74/84] Bump openpype version to 3.15.12-quad.3.12 --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 3abfa89cbb5..2729efb7b47 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.11" +__version__ = "3.15.12-quad.3.12" From 365cce0d55493a3e992ba52bdbe39a348fffb0e0 Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Wed, 25 Oct 2023 18:35:55 +0200 Subject: [PATCH 75/84] Bump openpype version to 3.15.12-quad.3.13 --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 2729efb7b47..79d663c72d2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.12-quad.3.12" +__version__ = "3.15.12-quad.3.13" From f7e5cd60366ef0726b46cb715b7f1f875a9e1c62 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 26 Oct 2023 11:05:56 +0200 Subject: [PATCH 76/84] block the status change of the task in ftrack post launch hook when opening a file if the status is set as completed --- .../modules/ftrack/launch_hooks/post_ftrack_changes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index 86ecffd5b80..d0e02b95839 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -141,6 +141,13 @@ def ftrack_status_change(self, session, entity, project_name): break try: + # Cancel the status change if the current status is "Completed" + if actual_status.lower() == "completed": + self.log.debug( + f"Ftrack status is 'Completed' for {ent_path}. " + "No status change." + ) + break query = "Status where name is \"{}\"".format( next_status_name ) From 13fe2045b47803001386bca835d127364a340ca0 Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Thu, 26 Oct 2023 15:40:48 +0200 Subject: [PATCH 77/84] #159: bugfixe: integrate_kitsu_note - re-add 'review' check because renders publish were always triggered. Instead add a double verification with 'kitsureview' tag --- .../tvpaint/plugins/publish/collect_render_instances.py | 4 ---- .../kitsu/plugins/publish/integrate_kitsu_note.py | 9 ++++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index 8974c7d7b33..c558d5e4ef9 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -123,7 +123,3 @@ def _collect_data_for_review(self, instance): instance.data["ignoreLayersTransparency"] = ( self.ignore_render_pass_transparency ) - - print('###\n###\n###') - print('IGNORE LAYERS TRANSPARENCY') - print(instance.data["ignoreLayersTransparency"]) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 874ca382e0c..d686757159c 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -55,9 +55,12 @@ def process(self, context): families = set( [instance.data["family"]] + instance.data.get("families", []) ) - print("\n####\n###") - print(families) - if "render" not in families: + representation = instance.data.get("representations", []) + + # Subset should have a review or a kitsureview tag + is_review = "review" in families + is_kitsu_review = 'kitsureview' in representation.get("tags", []) + if not is_review and not is_kitsu_review: continue kitsu_task = instance.data.get("kitsu_task") From cf32a8d3e0629132171848eb1166bce8d884b9fe Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Thu, 26 Oct 2023 17:12:54 +0200 Subject: [PATCH 78/84] #159: bugfixe: intergate_kitsu_note - update how OP is searching for sequence tag in representations --- .../kitsu/plugins/publish/integrate_kitsu_note.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index d686757159c..1bc497afda7 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -48,6 +48,12 @@ def replace_missing_key(match): pattern = r"\{([^}]*)\}" return re.sub(pattern, replace_missing_key, template) + def _get_representations_with_sequence_tag(self, representations): + return [ + repr for repr in representations + if 'sequence' in repr.get("tags", False) + ] + def process(self, context): for instance in context: # Check if instance is a review by checking its family @@ -55,11 +61,11 @@ def process(self, context): families = set( [instance.data["family"]] + instance.data.get("families", []) ) - representation = instance.data.get("representations", []) + representations = instance.data.get("representations", []) # Subset should have a review or a kitsureview tag + is_kitsu_review = self._get_representations_with_sequence_tag(representations) is_review = "review" in families - is_kitsu_review = 'kitsureview' in representation.get("tags", []) if not is_review and not is_kitsu_review: continue From 932dcb2875ad78c4ccc732eb777d16bfe69d01c1 Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Thu, 26 Oct 2023 17:52:14 +0200 Subject: [PATCH 79/84] #159: bugfixe: integrate_kitsu_note - return empty list instead of False value when searchng for sequence in tags --- openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 1bc497afda7..fbef3a59615 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -51,7 +51,7 @@ def replace_missing_key(match): def _get_representations_with_sequence_tag(self, representations): return [ repr for repr in representations - if 'sequence' in repr.get("tags", False) + if 'sequence' in repr.get("tags", []) ] def process(self, context): From 0a2dd2f50c5140c27d1a4de4aa77943dede8109f Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Fri, 27 Oct 2023 10:22:41 +0200 Subject: [PATCH 80/84] #159: bugfixe: try to get and convert keep_layers_transparency attribute if ignore_layers_transparency is not found --- .../publish/collect_render_instances.py | 26 ++++++++++++++----- .../plugins/publish/extract_sequence.py | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py index c558d5e4ef9..6cc92194ece 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_render_instances.py @@ -108,18 +108,32 @@ def _collect_data_for_render_scene(self, instance): **prepare_template_data({"renderpass": render_pass_name}) ) + def _get_ignore_transparency_option(self, instance): + ignore_transparency = instance.data["creator_attributes"].get( + "ignore_layers_transparency", None + ) + + if not ignore_transparency: + keep_transparency = instance.data["creator_attributes"].get( + "keep_layers_transparency", None + ) + return not keep_transparency + + else: + return ignore_transparency + + def _collect_data_for_review(self, instance): instance.data["layers"] = copy.deepcopy( instance.context.data["layersData"] ) - transparency_from_creator = instance.data["creator_attributes"].get( - "ignore_layers_transparency", None) - if transparency_from_creator: + ignore_transparency = self._get_ignore_transparency_option(instance) + if ignore_transparency: instance.data["ignoreLayersTransparency"] = ( - transparency_from_creator - ) + ignore_transparency + ) else: instance.data["ignoreLayersTransparency"] = ( self.ignore_render_pass_transparency - ) + ) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index f982a628e46..a69e5618b13 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -115,7 +115,7 @@ def process(self, instance): is_review = instance.data["family"] == "review" publish_sequence_with_transparency = ( instance.data["creator_identifier"] == "publish.sequence" and \ - not instance.data["creator_attributes"].get('ignore_layers_transparency', False) + not ignore_layers_transparency ) if is_review or publish_sequence_with_transparency: From aadbc605133282a86adac7fe6be27856620ec0aa Mon Sep 17 00:00:00 2001 From: Guilhemz Date: Fri, 27 Oct 2023 12:46:29 +0200 Subject: [PATCH 81/84] #161: bugfix: tvPaint - when getting current workfile, return a None value instead of an antislashewhen current scene is empty / not saved --- openpype/hosts/tvpaint/api/pipeline.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 58fbd095452..bce2dbafb43 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -164,7 +164,14 @@ def work_root(self, session): return session["AVALON_WORKDIR"] def get_current_workfile(self): - return execute_george("tv_GetProjectName") + # tvPaint return a '\' character when no scene is currently + # opened instead of a None value, which causes interferences + # in OpenPype's core code. So we check the returned value and + # send None if this character is retrieved. + current_workfile = execute_george("tv_GetProjectName") + if current_workfile == '\\': + current_workfile = None + return current_workfile def workfile_has_unsaved_changes(self): return None From 3da2faa06477e2f9d79685469d8d2c829961acfa Mon Sep 17 00:00:00 2001 From: ccaillot Date: Wed, 18 Oct 2023 17:25:39 +0200 Subject: [PATCH 82/84] Apply handle to playback start and end frame --- openpype/hosts/maya/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 2519c4d6cc3..21623df4dd7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2256,6 +2256,7 @@ def get_frame_range(include_animation_range=False): "handleStart": handle_start, "handleEnd": handle_end } + if include_animation_range: # The animation range values are only included to define whether # the Maya time slider should include the handles or not. @@ -2279,6 +2280,8 @@ def get_frame_range(include_animation_range=False): animation_start -= int(handle_start) animation_end += int(handle_end) + frame_range["frameStart"] = animation_start + frame_range["frameEnd"] = animation_end frame_range["animationStart"] = animation_start frame_range["animationEnd"] = animation_end From e9c1d3d0f516c630805f5f1b81bd1ebf13aef2e3 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 30 Oct 2023 10:37:27 +0100 Subject: [PATCH 83/84] ignore the status change if the current status is in the ignored statuses list from project settings --- .../launch_hooks/post_ftrack_changes.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index d0e02b95839..0aa895546e3 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -125,6 +125,21 @@ def ftrack_status_change(self, session, entity, project_name): ent_path = "/".join( [ent["name"] for ent in entity["link"]] ) + + change_statuses_settings = project_settings["ftrack"]["user_handlers"][ + "application_launch_statuses" + ] + ignored_statuses = [status.lower() for status in change_statuses_settings["ignored_statuses"]] # noqa + + if change_statuses_settings["enabled"] and actual_status in ignored_statuses: # noqa + self.log.debug( + "Ftrack status is '{}' for {}. " + "No status change.".format( + actual_status, ent_path + ) + ) + return + while True: next_status_name = None for key, value in status_mapping.items(): @@ -141,13 +156,6 @@ def ftrack_status_change(self, session, entity, project_name): break try: - # Cancel the status change if the current status is "Completed" - if actual_status.lower() == "completed": - self.log.debug( - f"Ftrack status is 'Completed' for {ent_path}. " - "No status change." - ) - break query = "Status where name is \"{}\"".format( next_status_name ) From 2b073b4d24f26d50e38775d69105350be98dd88e Mon Sep 17 00:00:00 2001 From: BenSouchet Date: Mon, 30 Oct 2023 16:27:43 +0100 Subject: [PATCH 84/84] Add branch 4-enhancement-allow-hiero-to-publish-using-symlinks into 3.15.12-quad.3.13 --- openpype/hosts/hiero/api/plugin.py | 12 ++++-- .../hiero/plugins/create/create_shot_clip.py | 2 +- openpype/plugins/publish/integrate.py | 38 ++++++++++++------- .../defaults/project_settings/global.json | 5 ++- .../schemas/schema_global_tools.json | 14 +++++++ 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index e76c87f7a18..6f84d83e3cc 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -709,6 +709,9 @@ def __init__(self, cls, track_item, **kwargs): # adding ui inputs if any self.ui_inputs = kwargs.get("ui_inputs", {}) + project_settings = get_current_project_settings() + self.symlink = project_settings["hiero"]["create"]["CreateShotClip"]["symlink"] # noqa + # populate default data before we get other attributes self._populate_track_item_default_data() @@ -756,7 +759,6 @@ def convert(self): def _populate_track_item_default_data(self): """ Populate default formatting data from track item. """ - symlink = self.ui_inputs['hierarchyData']['value'].get('symlink') self.track_item_default_data = { "_folder_": "shots", @@ -765,7 +767,7 @@ def _populate_track_item_default_data(self): "_clip_": self.ti_name, "_trackIndex_": self.track_index, "_clipIndex_": self.ti_index, - "_symlink_": symlink["value"] + "_symlink_": self.symlink } def _populate_attributes(self): @@ -789,7 +791,11 @@ def _populate_attributes(self): self.hierarchy_data = self.ui_inputs.get( "hierarchyData", {}).get("value") or \ self.track_item_default_data.copy() - self.hierarchy_data["symlink"].update({"value": "{_symlink_}"}) + + ui_symlink = self.ui_inputs.get( + "hierarchyData", {}).get("value").get("symlink").get("value") + self.hierarchy_data["symlink"].update({"value": str(ui_symlink)}) + self.count_from = self.ui_inputs.get( "countFrom", {}).get("value") or self.count_from_default self.count_steps = self.ui_inputs.get( diff --git a/openpype/hosts/hiero/plugins/create/create_shot_clip.py b/openpype/hosts/hiero/plugins/create/create_shot_clip.py index e8d78e3f7ae..f39aa9ee6c2 100644 --- a/openpype/hosts/hiero/plugins/create/create_shot_clip.py +++ b/openpype/hosts/hiero/plugins/create/create_shot_clip.py @@ -108,7 +108,7 @@ class CreateShotClip(phiero.Creator): "type": "QCheckBox", "label": "Publish using symlink", "target": "tag", - "toolTip": "Publish symlinks and not copied files", + "toolTip": "Publish symlinks, don't copy files", "order": 5} } }, diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6462666b62a..cce38dba52a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -301,11 +301,6 @@ def register(self, instance, file_transactions, filtered_repres): ) } - is_symlink_mode = False - hierarchy_data = instance.data.get("hierarchyData") - if hierarchy_data: - is_symlink_mode = hierarchy_data.get("symlink") - # Prepare all representations prepared_representations = [] for repre in filtered_repres: @@ -320,14 +315,8 @@ def register(self, instance, file_transactions, filtered_repres): for src, dst in prepared["transfers"]: # todo: add support for hardlink transfers - if is_symlink_mode: - file_transactions.add( - src, - dst, - mode=FileTransaction.MODE_SYMLINK - ) - else: - file_transactions.add(src, dst) + file_transaction_mode = self.get_file_transaction_mode(instance, src) + file_transactions.add(src, dst, mode=file_transaction_mode) prepared_representations.append(prepared) @@ -439,6 +428,29 @@ def register(self, instance, file_transactions, filtered_repres): self.log.info("Registered {} representations" "".format(len(prepared_representations))) + @staticmethod + def get_file_transaction_mode(instance, src): + import re + is_symlink_mode_enable = False + hierarchy_data = instance.data.get("hierarchyData") + if hierarchy_data: + is_symlink_mode_enable = (hierarchy_data.get("symlink") == "True") + + if not is_symlink_mode_enable: + return FileTransaction.MODE_COPY + + pattern = instance.context.data["project_settings"]["global"]["tools"]["publish"]["symlink"][ + "file_regex_pattern"] + if not pattern: + is_valid_symlink_path = True + else: + is_valid_symlink_path = bool(re.match(pattern, src)) + + if is_symlink_mode_enable and is_valid_symlink_path: + return FileTransaction.MODE_SYMLINK + + return FileTransaction.MODE_COPY + def prepare_subset(self, instance, op_session, project_name): asset_doc = instance.data["assetEntity"] subset_name = instance.data["subset"] diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 802b964375b..2f645d9bc9b 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -625,7 +625,10 @@ "template_name": "simpleUnrealTextureHero" } ], - "custom_staging_dir_profiles": [] + "custom_staging_dir_profiles": [], + "symlink": { + "file_regex_pattern": "^[^\\/\\\\]*[\\/\\\\]prod[\\/\\\\].*$" + } } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index 85ec482e735..c5b1b6723fe 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -473,6 +473,20 @@ } ] } + }, + { + "type": "dict", + "collapsible": true, + "key": "symlink", + "label": "Symlink", + "is_group": true, + "children": [ + { + "type": "text", + "key": "file_regex_pattern", + "label": "File Regex Pattern" + } + ] } ] }