From 8267736bed9968a314658b45de2cbc904c0f2547 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:24:23 +0200 Subject: [PATCH 01/23] Fix Fusion 18.6+ support: Avoid issues with Fusion's `BlackmagicFusion.PyRemoteObject` instances being unhashable. ```python Traceback (most recent call last): File "E:\openpype\OpenPype\.venv\lib\site-packages\pyblish\plugin.py", line 527, in __explicit_process runner(*args) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 61, in process result = self.render(instance) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 118, in render with enabled_savers(current_comp, savers_to_render): File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\contextlib.py", line 119, in __enter__ return next(self.gen) File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 33, in enabled_savers original_states[saver] = original_state TypeError: unhashable type: 'BlackmagicFusion.PyRemoteObject' ``` --- .../hosts/fusion/plugins/publish/extract_render_local.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 25c101cf00..e7bf010a9d 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -25,12 +25,13 @@ def enabled_savers(comp, savers): """ passthrough_key = "TOOLB_PassThrough" original_states = {} + savers_by_name = {saver.Name: saver for saver in savers} enabled_save_names = {saver.Name for saver in savers} try: all_savers = comp.GetToolList(False, "Saver").values() for saver in all_savers: original_state = saver.GetAttrs()[passthrough_key] - original_states[saver] = original_state + original_states[saver.Name] = original_state # The passthrough state we want to set (passthrough != enabled) state = saver.Name not in enabled_save_names @@ -38,7 +39,8 @@ def enabled_savers(comp, savers): saver.SetAttrs({passthrough_key: state}) yield finally: - for saver, original_state in original_states.items(): + for saver_name, original_state in original_states.items(): + saver = savers_by_name.get(saver_name) saver.SetAttrs({"TOOLB_PassThrough": original_state}) From 74070e0f25d9b53ec16512057e8e08ad228040ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:28:46 +0200 Subject: [PATCH 02/23] Be more explicit that key should exist --- openpype/hosts/fusion/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index e7bf010a9d..7cc03410c6 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -40,7 +40,7 @@ def enabled_savers(comp, savers): yield finally: for saver_name, original_state in original_states.items(): - saver = savers_by_name.get(saver_name) + saver = savers_by_name[saver_name] saver.SetAttrs({"TOOLB_PassThrough": original_state}) From 33ebd706a9232a3d8b0c34f79eb46474fde2f640 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 Oct 2023 16:31:25 +0200 Subject: [PATCH 03/23] Correctly produce the `savers_by_name` dict to include all savers --- .../fusion/plugins/publish/extract_render_local.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 7cc03410c6..08d608139d 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -25,16 +25,18 @@ def enabled_savers(comp, savers): """ passthrough_key = "TOOLB_PassThrough" original_states = {} - savers_by_name = {saver.Name: saver for saver in savers} - enabled_save_names = {saver.Name for saver in savers} + enabled_saver_names = {saver.Name for saver in savers} + + all_savers = comp.GetToolList(False, "Saver").values() + savers_by_name = {saver.Name: saver for saver in all_savers} + try: - all_savers = comp.GetToolList(False, "Saver").values() for saver in all_savers: original_state = saver.GetAttrs()[passthrough_key] original_states[saver.Name] = original_state # The passthrough state we want to set (passthrough != enabled) - state = saver.Name not in enabled_save_names + state = saver.Name not in enabled_saver_names if state != original_state: saver.SetAttrs({passthrough_key: state}) yield From 382ad931c670adbdd52fdf8996bb1d4115d37744 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Nov 2023 14:13:08 +0100 Subject: [PATCH 04/23] resolve: frame duration and handles operation --- openpype/hosts/resolve/api/plugin.py | 53 ++++++++++++++++------------ 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 5c4a92df89..0f6f4d14bd 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -405,26 +405,41 @@ class ClipLoader: self.active_bin ) _clip_property = media_pool_item.GetClipProperty + source_in = int(_clip_property("Start")) + source_out = int(_clip_property("End")) + source_duration = int(_clip_property("Frames")) - # get handles + # get version data frame data from db + frame_start = self.data["versionData"].get("frameStart") + frame_end = self.data["versionData"].get("frameEnd") handle_start = self.data["versionData"].get("handleStart") handle_end = self.data["versionData"].get("handleEnd") - if handle_start is None: - handle_start = int(self.data["assetData"]["handleStart"]) - if handle_end is None: - handle_end = int(self.data["assetData"]["handleEnd"]) - # check frame duration from versionData or assetData - frame_start = self.data["versionData"].get("frameStart") - if frame_start is None: - frame_start = self.data["assetData"]["frameStart"] + # check if source duration is shorter than db frame duration + source_with_handles = True + # make sure all frame data is available + if ( + frame_start is None or + frame_end is None or + handle_start is None or + handle_end is None + ): + # if not then rather assume that source has no handles + source_with_handles = False + else: + # calculate db frame duration + db_frame_duration = ( + # include handles + int(handle_start) + int(handle_end) + + # include frame duration + (int(frame_end) - int(frame_start) + 1) + ) - # check frame duration from versionData or assetData - frame_end = self.data["versionData"].get("frameEnd") - if frame_end is None: - frame_end = self.data["assetData"]["frameEnd"] - - db_frame_duration = int(frame_end) - int(frame_start) + 1 + # compare source duration with db frame duration + # and assume that source has no handles if source duration + # is shorter than db frame duration + if source_duration < db_frame_duration: + source_with_handles = False # get timeline in timeline_start = self.active_timeline.GetStartFrame() @@ -436,14 +451,6 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) - source_duration = int(_clip_property("Frames")) - - # check if source duration is shorter than db frame duration - source_with_handles = True - if source_duration < db_frame_duration: - source_with_handles = False # only exclude handles if source has no handles or # if user wants to load without handles From e5e278201b359772e51ae7dafae188753de06a3e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Nov 2023 16:18:04 +0100 Subject: [PATCH 05/23] better code explanation --- openpype/hosts/resolve/api/plugin.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 0f6f4d14bd..09c5a69d73 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -415,6 +415,19 @@ class ClipLoader: handle_start = self.data["versionData"].get("handleStart") handle_end = self.data["versionData"].get("handleEnd") + """ + There are cases where representation could be published without + handles if the "Extract review output tags" is set to "no_handles". + This would result in a shorter source duration compared to the + db frame-range. In such cases, we need to assume that the source + has no handles. + + To address this, we should compare the duration of the source + frame with the db frame-range. The duration of the db frame-range + should be calculated from the version data. If, for any reason, + the frame data is missing in the version data, we should again + assume that the source has no handles. + """ # check if source duration is shorter than db frame duration source_with_handles = True # make sure all frame data is available @@ -771,7 +784,7 @@ class PublishClip: # increasing steps by index of rename iteration self.count_steps *= self.rename_index - hierarchy_formatting_data = dict() + hierarchy_formatting_data = {} _data = self.timeline_item_default_data.copy() if self.ui_inputs: # adding tag metadata from ui @@ -867,7 +880,7 @@ class PublishClip: def _convert_to_entity(self, key): """ Converting input key to key with type. """ # convert to entity type - entity_type = self.types.get(key, None) + entity_type = self.types.get(key) assert entity_type, "Missing entity type for `{}`".format( key From 2c0d1b5c2bd3efb9e0740b0d5324a588533459e1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 13:56:08 +0100 Subject: [PATCH 06/23] improving the logic for handles operation --- openpype/hosts/resolve/api/plugin.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 09c5a69d73..886615c7aa 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -410,10 +410,16 @@ class ClipLoader: source_duration = int(_clip_property("Frames")) # get version data frame data from db - frame_start = self.data["versionData"].get("frameStart") - frame_end = self.data["versionData"].get("frameEnd") - handle_start = self.data["versionData"].get("handleStart") - handle_end = self.data["versionData"].get("handleEnd") + version_data = self.data["versionData"] + frame_start = version_data.get("frameStart") + frame_end = version_data.get("frameEnd") + + if self.with_handles: + handle_start = version_data.get("handleStart") or 0 + handle_end = version_data.get("handleEnd") or 0 + else: + handle_start = 0 + handle_end = 0 """ There are cases where representation could be published without @@ -434,8 +440,8 @@ class ClipLoader: if ( frame_start is None or frame_end is None or - handle_start is None or - handle_end is None + handle_start is 0 or + handle_end is 0 ): # if not then rather assume that source has no handles source_with_handles = False @@ -464,12 +470,11 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - # only exclude handles if source has no handles or # if user wants to load without handles if ( - not self.with_handles - or not source_with_handles + not self.with_handles # set by user + or not source_with_handles # result of source duration check ): source_in += handle_start source_out -= handle_end From 6fa92ebc14204c3c723a0b2a1d42ba492d10ff30 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 14:40:33 +0100 Subject: [PATCH 07/23] resolve: clip data should be integer rather then timecode --- openpype/hosts/resolve/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index aef9caca78..3866477c77 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -298,7 +298,7 @@ def create_timeline_item( if source_end: clip_data["endFrame"] = source_end if timecode_in: - clip_data["recordFrame"] = timecode_in + clip_data["recordFrame"] = timeline_in # add to timeline media_pool.AppendToTimeline([clip_data]) From 85bad0ae3d6f43732b9087583af2e09de55fed40 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Nov 2023 14:42:30 +0100 Subject: [PATCH 08/23] hound --- openpype/hosts/resolve/api/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 886615c7aa..7cf5913b67 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -440,8 +440,8 @@ class ClipLoader: if ( frame_start is None or frame_end is None or - handle_start is 0 or - handle_end is 0 + handle_start == 0 or + handle_end == 0 ): # if not then rather assume that source has no handles source_with_handles = False @@ -473,8 +473,8 @@ class ClipLoader: # only exclude handles if source has no handles or # if user wants to load without handles if ( - not self.with_handles # set by user - or not source_with_handles # result of source duration check + not self.with_handles # set by user + or not source_with_handles # result of source duration check ): source_in += handle_start source_out -= handle_end From c0926104d59104cbc79d19296da70844ccc3fa21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 16 Nov 2023 18:29:31 +0100 Subject: [PATCH 09/23] :bug: handle empty materials list --- openpype/hosts/maya/plugins/publish/collect_look.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index db042963c6..16ae20e0ae 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -367,7 +367,11 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.debug("Found the following sets:\n{}".format(look_sets)) # Get the entire node chain of the look sets # history = cmds.listHistory(look_sets, allConnections=True) - history = cmds.listHistory(materials, allConnections=True) + # if materials list is empty, listHistory() will crash with + # RuntimeError + history = [] + if materials: + history = cmds.listHistory(materials, allConnections=True) # Since we retrieved history only of the connected materials # connected to the look sets above we now add direct history From 319109be593940d282d9beb56a26b70e0cd76ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 24 Nov 2023 17:47:23 +0100 Subject: [PATCH 10/23] :bug: fix issue with render sets collection and inventory action --- .../plugins/inventory/import_modelrender.py | 11 ++---- .../maya/plugins/publish/collect_look.py | 39 +++++++++++++------ openpype/pipeline/actions.py | 7 ++++ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index 4db8c4f2f6..3b30695146 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -33,7 +33,7 @@ class ImportModelRender(InventoryAction): ) def process(self, containers): - from maya import cmds + from maya import cmds # noqa: F401 project_name = get_current_project_name() for container in containers: @@ -66,7 +66,7 @@ class ImportModelRender(InventoryAction): None """ - from maya import cmds + from maya import cmds # noqa: F401 project_name = get_current_project_name() repre_docs = get_representations( @@ -85,12 +85,7 @@ class ImportModelRender(InventoryAction): if scene_type_regex.fullmatch(repre_name): look_repres.append(repre_doc) - # QUESTION should we care if there is more then one look - # representation? (since it's based on regex match) - look_repre = None - if look_repres: - look_repre = look_repres[0] - + look_repre = look_repres[0] if look_repres else None # QUESTION shouldn't be json representation validated too? if not look_repre: print("No model render sets for this model version..") diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 16ae20e0ae..a9e967a094 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -69,9 +69,7 @@ def get_attributes(dictionary, attr, node=None): else: val = dictionary.get(attr, []) - if not isinstance(val, list): - return [val] - return val + return val if isinstance(val, list) else [val] def get_look_attrs(node): @@ -106,7 +104,7 @@ def get_look_attrs(node): def node_uses_image_sequence(node, node_path): - # type: (str) -> bool + # type: (str, str) -> bool """Return whether file node uses an image sequence or single image. Determine if a node uses an image sequence or just a single image, @@ -114,6 +112,7 @@ def node_uses_image_sequence(node, node_path): Args: node (str): Name of the Maya node + node_path (str): The file path of the node Returns: bool: True if node uses an image sequence @@ -247,7 +246,7 @@ def get_file_node_files(node): # For sequences get all files and filter to only existing files result = [] - for index, path in enumerate(paths): + for path in paths: if node_uses_image_sequence(node, path): glob_pattern = seq_to_glob(path) result.extend(glob.glob(glob_pattern)) @@ -358,6 +357,11 @@ class CollectLook(pyblish.api.InstancePlugin): for attr in shader_attrs: if cmds.attributeQuery(attr, node=look, exists=True): existing_attrs.append("{}.{}".format(look, attr)) + + print("-"*100) + print("existing_attrs: {}".format(existing_attrs)) + print("-"*100) + materials = cmds.listConnections(existing_attrs, source=True, destination=False) or [] @@ -377,13 +381,24 @@ class CollectLook(pyblish.api.InstancePlugin): # connected to the look sets above we now add direct history # for some of the look sets directly # handling render attribute sets - render_set_types = [ - "VRayDisplacement", - "VRayLightMesh", - "VRayObjectProperties", - "RedshiftObjectId", - "RedshiftMeshParameters", - ] + + # this needs to be done like this now, because Maya + # (at least 2024) crashes with Warning when render set type + # isn't available. cmds.ls() will return empty list + render_set_types = [] + if cmds.pluginInfo("vrayformaya", query=True, loaded=True): + render_set_types += [ + "VRayDisplacement", + "VRayLightMesh", + "VRayObjectProperties", + ] + + if cmds.pluginInfo("redshift4maya", query=True, loaded=True): + render_set_types += [ + "RedshiftObjectId", + "RedshiftMeshParameters", + ] + render_sets = cmds.ls(look_sets, type=render_set_types) if render_sets: history.extend( diff --git a/openpype/pipeline/actions.py b/openpype/pipeline/actions.py index feb1bd05d2..d89e2096ef 100644 --- a/openpype/pipeline/actions.py +++ b/openpype/pipeline/actions.py @@ -7,6 +7,8 @@ from openpype.pipeline.plugin_discover import ( deregister_plugin_path ) +from .load.utils import get_representation_path_from_context + class LauncherAction(object): """A custom action available""" @@ -100,6 +102,11 @@ class InventoryAction(object): """ return True + @classmethod + def filepath_from_context(cls, context): + return get_representation_path_from_context(context) + + # Launcher action def discover_launcher_actions(): From 6b4d0487acea25c67c1b4d1d64dfbd2e63fb7308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 24 Nov 2023 17:48:22 +0100 Subject: [PATCH 11/23] :dog: remove debug prints --- openpype/hosts/maya/plugins/publish/collect_look.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index a9e967a094..11e87a8295 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -358,10 +358,6 @@ class CollectLook(pyblish.api.InstancePlugin): if cmds.attributeQuery(attr, node=look, exists=True): existing_attrs.append("{}.{}".format(look, attr)) - print("-"*100) - print("existing_attrs: {}".format(existing_attrs)) - print("-"*100) - materials = cmds.listConnections(existing_attrs, source=True, destination=False) or [] From ddefee8f0e05320ea9c3d8a5d63d196517db747b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Nov 2023 03:26:09 +0000 Subject: [PATCH 12/23] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e2afcdaac7..f59eb1edb1 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.17.7-nightly.3 - 3.17.7-nightly.2 - 3.17.7-nightly.1 - 3.17.6 @@ -134,7 +135,6 @@ body: - 3.15.2-nightly.6 - 3.15.2-nightly.5 - 3.15.2-nightly.4 - - 3.15.2-nightly.3 validations: required: true - type: dropdown From 12c8e9852c58011acf86e923276a0b946f14c9ea Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:29:10 +0800 Subject: [PATCH 13/23] make sure basename of the filenames for 'files' in representation --- .../hosts/maya/plugins/publish/extract_redshift_proxy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 3868270b79..d601267802 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,11 +50,12 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format(root, str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format( + os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, - int(instance.data["step"]), + int(instance.data["step"]) )] # vertex_colors = instance.data.get("vertexColors", False) From 90a62c35dcde56f68a9149d6aae45fde11a998e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:32:21 +0800 Subject: [PATCH 14/23] hound --- openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index d601267802..67533cad35 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,8 +50,7 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format( - os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, From de572f59f9d67a9c1e4b7407776d088fb15b9a11 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 17:33:28 +0800 Subject: [PATCH 15/23] hound --- openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 67533cad35..7fc8760a70 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -50,7 +50,7 @@ class ExtractRedshiftProxy(publish.Extractor): # Padding is taken from number of digits of the end_frame. # Not sure where Redshift is taking it. repr_files = [ - "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 + "{}.{}{}".format(os.path.basename(root), str(frame).rjust(4, "0"), ext) # noqa: E501 for frame in range( int(start_frame), int(end_frame) + 1, From 1d885222870b8ebf1f7f92f3d162ab2d666b1315 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 18:39:09 +0800 Subject: [PATCH 16/23] make sure default shader connection validator not checking when the maya version is 2024 --- .../publish/validate_look_default_shaders_connections.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 0109f6ebd5..464c3facc2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -45,6 +45,10 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): # Process as usual invalid = list() + if int(cmds.about(version=True)) >= 2024: + self.log.debug("IntialShadingGroup no longer connected to the default shader" + " in Maya 2024. Skipping Look Default Shader Connections..") + return for plug, input_node in self.DEFAULTS: inputs = cmds.listConnections(plug, source=True, From d0a423423ca8e88a40a1d4b5dfcbbd206cbe9da5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 18:44:14 +0800 Subject: [PATCH 17/23] hound --- .../publish/validate_look_default_shaders_connections.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 464c3facc2..69a2081689 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -46,8 +46,9 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): # Process as usual invalid = list() if int(cmds.about(version=True)) >= 2024: - self.log.debug("IntialShadingGroup no longer connected to the default shader" - " in Maya 2024. Skipping Look Default Shader Connections..") + self.log.debug("IntialShadingGroup no longer " + "connected to the default shader in Maya 2024" + "Skipping Look Default Shader Connections..") return for plug, input_node in self.DEFAULTS: inputs = cmds.listConnections(plug, From c7529c2b56967bc2c6dc92470224b4bee4952c52 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Nov 2023 21:58:37 +0800 Subject: [PATCH 18/23] get the logic from the colorbleed's branch. thanks for @BigRoy contribution --- ...lidate_look_default_shaders_connections.py | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index 69a2081689..c3edf5f1c3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -3,65 +3,76 @@ from maya import cmds import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, + RepairContextAction, PublishValidationError ) -class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): +class ValidateLookDefaultShadersConnections(pyblish.api.ContextPlugin): """Validate default shaders in the scene have their default connections. - For example the lambert1 could potentially be disconnected from the - initialShadingGroup. As such it's not lambert1 that will be identified - as the default shader which can have unpredictable results. + For example the standardSurface1 or lambert1 (maya 2023 and before) could + potentially be disconnected from the initialShadingGroup. As such it's not + lambert1 that will be identified as the default shader which can have + unpredictable results. To fix the default connections need to be made again. See the logs for more details on which connections are missing. """ - order = ValidateContentsOrder + order = pyblish.api.ValidatorOrder - 0.4999 families = ['look'] hosts = ['maya'] label = 'Look Default Shader Connections' + actions = [RepairContextAction] # The default connections to check - DEFAULTS = [("initialShadingGroup.surfaceShader", "lambert1"), - ("initialParticleSE.surfaceShader", "lambert1"), - ("initialParticleSE.volumeShader", "particleCloud1") - ] + DEFAULTS = { + "initialShadingGroup.surfaceShader": ["standardSurface1.outColor", + "lambert1.outColor"], + "initialParticleSE.surfaceShader": ["standardSurface1.outColor", + "lambert1.outColor"], + "initialParticleSE.volumeShader": ["particleCloud1.outColor"] + } - def process(self, instance): + def process(self, context): - # Ensure check is run only once. We don't use ContextPlugin because - # of a bug where the ContextPlugin will always be visible. Even when - # the family is not present in an instance. - key = "__validate_look_default_shaders_connections_checked" - context = instance.context - is_run = context.data.get(key, False) - if is_run: - return - else: - context.data[key] = True + if self.get_invalid(): + raise PublishValidationError( + "Default shaders in your scene do not have their " + "default shader connections. Please repair them to continue." + ) + + @classmethod + def get_invalid(cls): # Process as usual invalid = list() - if int(cmds.about(version=True)) >= 2024: - self.log.debug("IntialShadingGroup no longer " - "connected to the default shader in Maya 2024" - "Skipping Look Default Shader Connections..") - return - for plug, input_node in self.DEFAULTS: + for plug, valid_inputs in cls.DEFAULTS.items(): inputs = cmds.listConnections(plug, source=True, - destination=False) or None - - if not inputs or inputs[0] != input_node: - self.log.error("{0} is not connected to {1}. " - "This can result in unexpected behavior. " - "Please reconnect to continue.".format( - plug, - input_node)) + destination=False, + plugs=True) or None + if not inputs or inputs[0] not in valid_inputs: + cls.log.error( + "{0} is not connected to {1}. This can result in " + "unexpected behavior. Please reconnect to continue." + "".format(plug, " or ".join(valid_inputs)) + ) invalid.append(plug) - if invalid: - raise PublishValidationError("Invalid connections.") + return invalid + + @classmethod + def repair(cls, context): + invalid = cls.get_invalid() + for plug in invalid: + valid_inputs = cls.DEFAULTS[plug] + for valid_input in valid_inputs: + if cmds.objExists(valid_input): + cls.log.info( + "Connecting {} -> {}".format(valid_input, plug) + ) + cmds.connectAttr(valid_input, plug, force=True) + break From 02342c864ddd62654699ae4351aeb9e76637bdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 27 Nov 2023 17:19:09 +0100 Subject: [PATCH 19/23] :recycle: filter render set types --- .../maya/plugins/publish/collect_look.py | 53 +++++++++---------- openpype/pipeline/actions.py | 1 - 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 11e87a8295..72682f7800 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -45,11 +45,23 @@ FILE_NODES = { "PxrTexture": "filename" } +RENDER_SET_TYPES = [ + "VRayDisplacement", + "VRayLightMesh", + "VRayObjectProperties", + "RedshiftObjectId", + "RedshiftMeshParameters", +] + # Keep only node types that actually exist all_node_types = set(cmds.allNodeTypes()) for node_type in list(FILE_NODES.keys()): if node_type not in all_node_types: FILE_NODES.pop(node_type) + +for node_type in RENDER_SET_TYPES: + if node_type not in all_node_types: + RENDER_SET_TYPES.remove(node_type) del all_node_types # Cache pixar dependency node types so we can perform a type lookup against it @@ -369,43 +381,30 @@ class CollectLook(pyblish.api.InstancePlugin): # history = cmds.listHistory(look_sets, allConnections=True) # if materials list is empty, listHistory() will crash with # RuntimeError - history = [] + history = set() if materials: - history = cmds.listHistory(materials, allConnections=True) + history = set( + cmds.listHistory(materials, allConnections=True)) # Since we retrieved history only of the connected materials # connected to the look sets above we now add direct history # for some of the look sets directly # handling render attribute sets - # this needs to be done like this now, because Maya - # (at least 2024) crashes with Warning when render set type + # Maya (at least 2024) crashes with Warning when render set type # isn't available. cmds.ls() will return empty list - render_set_types = [] - if cmds.pluginInfo("vrayformaya", query=True, loaded=True): - render_set_types += [ - "VRayDisplacement", - "VRayLightMesh", - "VRayObjectProperties", - ] - - if cmds.pluginInfo("redshift4maya", query=True, loaded=True): - render_set_types += [ - "RedshiftObjectId", - "RedshiftMeshParameters", - ] - - render_sets = cmds.ls(look_sets, type=render_set_types) - if render_sets: - history.extend( - cmds.listHistory(render_sets, - future=False, - pruneDagObjects=True) - or [] - ) + if RENDER_SET_TYPES: + render_sets = cmds.ls(look_sets, type=RENDER_SET_TYPES) + if render_sets: + history.update( + cmds.listHistory(render_sets, + future=False, + pruneDagObjects=True) + or [] + ) # Ensure unique entries only - history = list(set(history)) + history = list(history) files = cmds.ls(history, # It's important only node types are passed that diff --git a/openpype/pipeline/actions.py b/openpype/pipeline/actions.py index d89e2096ef..68533f7485 100644 --- a/openpype/pipeline/actions.py +++ b/openpype/pipeline/actions.py @@ -107,7 +107,6 @@ class InventoryAction(object): return get_representation_path_from_context(context) - # Launcher action def discover_launcher_actions(): return discover(LauncherAction) From efff32392375e8712ecc7f561e5a59f8893680ef Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Nov 2023 11:02:47 +0100 Subject: [PATCH 20/23] Harmony: Fix local rendering (#5953) * Fix local rendering It was throwing licence issue, but didn't produce anything. * Commenting out proto line This seems to be more stable in showing Openpype menu. For unknown reason menu creation started to fail to show up, this line caused crashes when this script was triggered manually inside of Harmony. --- openpype/hosts/harmony/api/TB_sceneOpened.js | 2 +- openpype/hosts/harmony/plugins/publish/extract_render.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index a284a6ec5c..48daf094dd 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -13,7 +13,7 @@ var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH'); LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH + '/openHarmony.js'; LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH.replace(/\\/g, "/"); include(LD_OPENHARMONY_PATH); -this.__proto__['$'] = $; +//this.__proto__['$'] = $; function Client() { var self = this; diff --git a/openpype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py index 5825d95a4a..96a375716b 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -59,8 +59,8 @@ class ExtractRender(pyblish.api.InstancePlugin): args = [application_path, "-batch", "-frames", str(frame_start), str(frame_end), - "-scene", scene_path] - self.log.info(f"running [ {application_path} {' '.join(args)}") + scene_path] + self.log.info(f"running: {' '.join(args)}") proc = subprocess.Popen( args, stdout=subprocess.PIPE, From 74566d8e07a6bfb89042f687b13151e7c47cc810 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Nov 2023 21:32:37 +0800 Subject: [PATCH 21/23] add label to MayaUSDReferenceLoader --- openpype/hosts/maya/plugins/load/load_reference.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 0d7f08d3c3..a4ab6c79c1 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -265,6 +265,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): class MayaUSDReferenceLoader(ReferenceLoader): """Reference USD file to native Maya nodes using MayaUSDImport reference""" + label = "Reference Maya USD" families = ["usd"] representations = ["usd"] extensions = {"usd", "usda", "usdc"} From 497e3ec9624e3d8090c9fa16caa49b1ab5a8c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 28 Nov 2023 21:32:42 +0100 Subject: [PATCH 22/23] Update openpype/hosts/resolve/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/resolve/api/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 9f1d8fc1c7..2f1f4d3545 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -439,10 +439,10 @@ class ClipLoader: source_with_handles = True # make sure all frame data is available if ( - frame_start is None or - frame_end is None or - handle_start == 0 or - handle_end == 0 + frame_start is None + or frame_end is None + or handle_start == 0 + or handle_end == 0 ): # if not then rather assume that source has no handles source_with_handles = False From 7c25e4a664fc062b581ee183329d2811f784ec7c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Nov 2023 21:49:05 +0100 Subject: [PATCH 23/23] fixing the logic from @bigroy https://github.com/ynput/OpenPype/pull/5863/files#r1381847587 --- openpype/hosts/resolve/api/plugin.py | 83 ++++++++++------------------ 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 2f1f4d3545..a00933405f 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -410,56 +410,38 @@ class ClipLoader: source_out = int(_clip_property("End")) source_duration = int(_clip_property("Frames")) - # get version data frame data from db - version_data = self.data["versionData"] - frame_start = version_data.get("frameStart") - frame_end = version_data.get("frameEnd") - - if self.with_handles: - handle_start = version_data.get("handleStart") or 0 - handle_end = version_data.get("handleEnd") or 0 - else: + if not self.with_handles: + # Load file without the handles of the source media + # We remove the handles from the source in and source out + # so that the handles are excluded in the timeline handle_start = 0 handle_end = 0 - """ - There are cases where representation could be published without - handles if the "Extract review output tags" is set to "no_handles". - This would result in a shorter source duration compared to the - db frame-range. In such cases, we need to assume that the source - has no handles. + # get version data frame data from db + version_data = self.data["versionData"] + frame_start = version_data.get("frameStart") + frame_end = version_data.get("frameEnd") - To address this, we should compare the duration of the source - frame with the db frame-range. The duration of the db frame-range - should be calculated from the version data. If, for any reason, - the frame data is missing in the version data, we should again - assume that the source has no handles. - """ - # check if source duration is shorter than db frame duration - source_with_handles = True - # make sure all frame data is available - if ( - frame_start is None - or frame_end is None - or handle_start == 0 - or handle_end == 0 - ): - # if not then rather assume that source has no handles - source_with_handles = False - else: - # calculate db frame duration - db_frame_duration = ( - # include handles - int(handle_start) + int(handle_end) + - # include frame duration - (int(frame_end) - int(frame_start) + 1) - ) - - # compare source duration with db frame duration - # and assume that source has no handles if source duration - # is shorter than db frame duration - if source_duration < db_frame_duration: - source_with_handles = False + # The version data usually stored the frame range + handles of the + # media however certain representations may be shorter because they + # exclude those handles intentionally. Unfortunately the + # representation does not store that in the database currently; + # so we should compensate for those cases. If the media is shorter + # than the frame range specified in the database we assume it is + # without handles and thus we do not need to remove the handles + # from source and out + if frame_start is not None and frame_end is not None: + # Version has frame range data, so we can compare media length + handle_start = version_data.get("handleStart", 0) + handle_end = version_data.get("handleEnd", 0) + frame_start_handle = frame_start - handle_start + frame_end_handle = frame_start + handle_end + database_frame_duration = int( + frame_end_handle - frame_start_handle + 1 + ) + if source_duration >= database_frame_duration: + source_in += handle_start + source_out -= handle_end # get timeline in timeline_start = self.active_timeline.GetStartFrame() @@ -471,15 +453,6 @@ class ClipLoader: timeline_in = int( timeline_start + self.data["assetData"]["clipIn"]) - # only exclude handles if source has no handles or - # if user wants to load without handles - if ( - not self.with_handles # set by user - or not source_with_handles # result of source duration check - ): - source_in += handle_start - source_out -= handle_end - # make track item from source in bin as item timeline_item = lib.create_timeline_item( media_pool_item,