From 723d7bbd7b87049f1be4c3fb87471aac09cbff9a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 19 Feb 2024 11:58:46 +0000 Subject: [PATCH 01/39] Porting from OpenPype --- client/ayon_core/hosts/nuke/api/utils.py | 33 ++++-- .../hosts/nuke/plugins/load/load_clip.py | 107 +++++++++++++----- 2 files changed, 98 insertions(+), 42 deletions(-) diff --git a/client/ayon_core/hosts/nuke/api/utils.py b/client/ayon_core/hosts/nuke/api/utils.py index d738ba5464..0a05077a68 100644 --- a/client/ayon_core/hosts/nuke/api/utils.py +++ b/client/ayon_core/hosts/nuke/api/utils.py @@ -103,33 +103,42 @@ def colorspace_exists_on_node(node, colorspace_name): except ValueError: # knob is not available on input node return False - all_clrs = get_colorspace_list(colorspace_knob) - return colorspace_name in all_clrs + return colorspace_name in get_colorspace_list(colorspace_knob) def get_colorspace_list(colorspace_knob): """Get available colorspace profile names + Because the values returned from colorspace_knob.values() do not correspond + to the value returned from colorspace_knob.value(), and the extracted + colorspace comes from using colorspace_knob.value(), we need to iterate + through all values to get the correct value. + + A code example of the above would be: + + for count, value in enumerate(colorspace_knob.values()): + colorspace_knob.setValue(count) + print(colorspace_knob.value() in colorspace_knob.values()) + Args: colorspace_knob (nuke.Knob): nuke knob object Returns: list: list of strings names of profiles """ + original_value = colorspace_knob.value() - all_clrs = list(colorspace_knob.values()) - reduced_clrs = [] + colorspaces = [] - if not colorspace_knob.getFlag(nuke.STRIP_CASCADE_PREFIX): - return all_clrs + try: + for count, _ in enumerate(colorspace_knob.values()): + colorspace_knob.setValue(count) + colorspaces.append(colorspace_knob.value()) + finally: + colorspace_knob.setValue(original_value) - # strip colorspace with nested path - for clrs in all_clrs: - clrs = clrs.split('/')[-1] - reduced_clrs.append(clrs) - - return reduced_clrs + return colorspaces def is_headless(): diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 8bce2eac6e..51cf5941ea 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -101,7 +101,6 @@ class LoadClip(plugin.NukeLoader): filepath = self.filepath_from_context(context) filepath = filepath.replace("\\", "/") - self.log.debug("_ filepath: {}".format(filepath)) start_at_workfile = options.get( "start_at_workfile", self.options_defaults["start_at_workfile"]) @@ -154,8 +153,8 @@ class LoadClip(plugin.NukeLoader): with viewer_update_and_undo_stop(): read_node["file"].setValue(filepath) - used_colorspace = self._set_colorspace( - read_node, version_data, representation["data"], filepath) + self.set_colorspace_to_node( + read_node, filepath, version, representation) self._set_range_to_node(read_node, first, last, start_at_workfile) @@ -180,8 +179,6 @@ class LoadClip(plugin.NukeLoader): colorspace = representation["data"].get(key) colorspace = colorspace or version_data.get(key) data_imprint["db_colorspace"] = colorspace - if used_colorspace: - data_imprint["used_colorspace"] = used_colorspace else: value_ = context["version"]['data'].get( key, str(None)) @@ -302,8 +299,8 @@ class LoadClip(plugin.NukeLoader): # to avoid multiple undo steps for rest of process # we will switch off undo-ing with viewer_update_and_undo_stop(): - used_colorspace = self._set_colorspace( - read_node, version_data, representation["data"], filepath) + self.set_colorspace_to_node( + read_node, filepath, version_doc, representation) self._set_range_to_node(read_node, first, last, start_at_workfile) @@ -320,10 +317,6 @@ class LoadClip(plugin.NukeLoader): "author": version_data.get("author") } - # add used colorspace if found any - if used_colorspace: - updated_dict["used_colorspace"] = used_colorspace - last_version_doc = get_last_version_by_subset_id( project_name, version_doc["parent"], fields=["_id"] ) @@ -350,6 +343,36 @@ class LoadClip(plugin.NukeLoader): self.set_as_member(read_node) + def set_colorspace_to_node( + self, + read_node, + filepath, + version_doc, + representation_doc, + ): + """Set colorspace to read node. + + Sets colorspace with available names validation. + + Args: + read_node (nuke.Node): The nuke's read node + filepath (str): file path + version_doc (dict): version document + representation_doc (dict): representation document + + """ + used_colorspace = self._get_colorspace_data( + version_doc, representation_doc, filepath) + + if ( + used_colorspace + and colorspace_exists_on_node(read_node, used_colorspace) + ): + self.log.info(f"Used colorspace: {used_colorspace}") + read_node["colorspace"].setValue(used_colorspace) + else: + self.log.info("Colorspace not set...") + def remove(self, container): read_node = container["node"] assert read_node.Class() == "Read", "Must be Read" @@ -450,25 +473,49 @@ class LoadClip(plugin.NukeLoader): return self.node_name_template.format(**name_data) - def _set_colorspace(self, node, version_data, repre_data, path): - output_color = None - path = path.replace("\\", "/") - # get colorspace - colorspace = repre_data.get("colorspace") - colorspace = colorspace or version_data.get("colorspace") + def _get_colorspace_data(self, version_doc, representation_doc, filepath): + """Get colorspace data from version and representation documents + + Args: + version_doc (dict): version document + representation_doc (dict): representation document + filepath (str): file path + + Returns: + Any[str,None]: colorspace name or None + """ + # Get backward compatible colorspace key. + colorspace = representation_doc["data"].get("colorspace") + self.log.debug( + f"Colorspace from representation colorspace: {colorspace}" + ) + + # Get backward compatible version data key if colorspace is not found. + colorspace = colorspace or version_doc["data"].get("colorspace") + self.log.debug(f"Colorspace from version colorspace: {colorspace}") + + # Get colorspace from representation colorspaceData if colorspace is + # not found. + colorspace_data = representation_doc["data"].get("colorspaceData", {}) + colorspace = colorspace or colorspace_data.get("colorspace") + self.log.debug( + f"Colorspace from representation colorspaceData: {colorspace}" + ) + + print(f"Colorspace found: {colorspace}") + + # check if any filerules are not applicable + new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa + filepath, "nuke", get_current_project_name() + ) + self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}") # colorspace from `project_settings/nuke/imageio/regexInputs` - iio_colorspace = get_imageio_input_colorspace(path) + old_parsed_colorspace = get_imageio_input_colorspace(filepath) + self.log.debug(f"Colorspace old filerules: {old_parsed_colorspace}") - # Set colorspace defined in version data - if ( - colorspace is not None - and colorspace_exists_on_node(node, str(colorspace)) - ): - node["colorspace"].setValue(str(colorspace)) - output_color = str(colorspace) - elif iio_colorspace is not None: - node["colorspace"].setValue(iio_colorspace) - output_color = iio_colorspace - - return output_color + return ( + new_parsed_colorspace + or old_parsed_colorspace + or colorspace + ) From 511e2047defb0bbd11bbb7d41dc13a047ba7c2e0 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Mar 2024 16:03:50 +0000 Subject: [PATCH 02/39] Update client/ayon_core/hosts/nuke/plugins/load/load_clip.py --- client/ayon_core/hosts/nuke/plugins/load/load_clip.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 51cf5941ea..686cd6eac6 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -502,8 +502,6 @@ class LoadClip(plugin.NukeLoader): f"Colorspace from representation colorspaceData: {colorspace}" ) - print(f"Colorspace found: {colorspace}") - # check if any filerules are not applicable new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa filepath, "nuke", get_current_project_name() From 091a706518e229fbb2c019351ef8dc73fa6b8f8b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 11:49:20 +0000 Subject: [PATCH 03/39] Dont brute force colorspaces list --- client/ayon_core/hosts/nuke/api/utils.py | 37 ++++++++++-------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/client/ayon_core/hosts/nuke/api/utils.py b/client/ayon_core/hosts/nuke/api/utils.py index 0a05077a68..80d719664a 100644 --- a/client/ayon_core/hosts/nuke/api/utils.py +++ b/client/ayon_core/hosts/nuke/api/utils.py @@ -1,4 +1,6 @@ import os +import re + import nuke from ayon_core import resources @@ -110,35 +112,28 @@ def colorspace_exists_on_node(node, colorspace_name): def get_colorspace_list(colorspace_knob): """Get available colorspace profile names - Because the values returned from colorspace_knob.values() do not correspond - to the value returned from colorspace_knob.value(), and the extracted - colorspace comes from using colorspace_knob.value(), we need to iterate - through all values to get the correct value. - - A code example of the above would be: - - for count, value in enumerate(colorspace_knob.values()): - colorspace_knob.setValue(count) - print(colorspace_knob.value() in colorspace_knob.values()) - Args: colorspace_knob (nuke.Knob): nuke knob object Returns: list: list of strings names of profiles """ - original_value = colorspace_knob.value() + results = [] - colorspaces = [] + # This pattern is to match with roles which uses an indentation and + # parentheses with original colorspace. The value returned from the + # colorspace is the string before the indentation, so we'll need to + # convert the values to match with value returned from the knob, + # ei. knob.value(). + pattern = r".*\t.* \(.*\)" + for colorspace in nuke.getColorspaceList(colorspace_knob): + match = re.search(pattern, colorspace) + if match: + results.append(colorspace.split("\t")[0]) + else: + results.append(colorspace) - try: - for count, _ in enumerate(colorspace_knob.values()): - colorspace_knob.setValue(count) - colorspaces.append(colorspace_knob.value()) - finally: - colorspace_knob.setValue(original_value) - - return colorspaces + return results def is_headless(): From 5120df1157b0b865430f36ebd213d3887731edb0 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 7 Mar 2024 12:14:44 +0000 Subject: [PATCH 04/39] Update client/ayon_core/hosts/nuke/api/utils.py Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/nuke/api/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/nuke/api/utils.py b/client/ayon_core/hosts/nuke/api/utils.py index 80d719664a..1bfc1919fa 100644 --- a/client/ayon_core/hosts/nuke/api/utils.py +++ b/client/ayon_core/hosts/nuke/api/utils.py @@ -129,7 +129,7 @@ def get_colorspace_list(colorspace_knob): for colorspace in nuke.getColorspaceList(colorspace_knob): match = re.search(pattern, colorspace) if match: - results.append(colorspace.split("\t")[0]) + results.append(colorspace.split("\t", 1)[0]) else: results.append(colorspace) From 2fe09689e8d317838ea218332ecd022f862f60d9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Mar 2024 21:57:24 +0100 Subject: [PATCH 05/39] missing import --- client/ayon_core/hosts/nuke/plugins/load/load_clip.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index a75703b718..98c3303eb8 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -11,6 +11,9 @@ from ayon_core.pipeline import ( get_current_project_name, get_representation_path, ) +from ayon_core.pipeline.colorspace import ( + get_imageio_file_rules_colorspace_from_filepath +) from ayon_core.hosts.nuke.api.lib import ( get_imageio_input_colorspace, maintained_selection From 1a0b23650883bdafd2585f6c5ecc6bbbb7c0c176 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 19:10:40 +0000 Subject: [PATCH 06/39] Remove representation unnessecary frameStart --- .../ayon_core/hosts/nuke/plugins/publish/collect_writes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py index 58afb2cd1f..745351dc49 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py @@ -194,7 +194,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, "frameEndHandle": last_frame, }) - # TODO temporarily set stagingDir as persistent for backward # compatibility. This is mainly focused on `renders`folders which # were previously not cleaned up (and could be used in read notes) @@ -269,10 +268,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, "tags": [] } - frame_start_str = self._get_frame_start_str(first_frame, last_frame) - - representation['frameStart'] = frame_start_str - # set slate frame collected_frames = self._add_slate_frame_to_collected_frames( instance, From 8b4085b3c3ad34125df14bbfd7b81ff2856caa61 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 19:10:58 +0000 Subject: [PATCH 07/39] Exclude instances nodes from slate collection. --- .../ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py index 3baa0cd9b5..ac30bd6051 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_slate_node.py @@ -17,7 +17,8 @@ class CollectSlate(pyblish.api.InstancePlugin): ( n_ for n_ in nuke.allNodes() if "slate" in n_.name().lower() - if not n_["disable"].getValue() + if not n_["disable"].getValue() and + "publish_instance" not in n_.knobs() # Exclude instance nodes. ), None ) From f957b9bf41dffe306c5ef4df0a6fdc1904887652 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Mar 2024 22:13:41 +0800 Subject: [PATCH 08/39] allow users to tweak material duplicate options in loader setting --- .../hosts/max/plugins/load/load_max_scene.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 39bb3b568d..915abc75a1 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -1,5 +1,6 @@ import os +from ayon_core.lib import EnumDef from ayon_core.hosts.max.api import lib from ayon_core.hosts.max.api.lib import ( unique_namespace, @@ -25,14 +26,28 @@ class MaxSceneLoader(load.LoaderPlugin): order = -8 icon = "code-fork" color = "green" + mtl_dup_default = "promptMtlDups" + mtl_dup_enum = ["promptMtlDups", "useMergedMtlDups", + "useSceneMtlDups", "renameMtlDups"] - def load(self, context, name=None, namespace=None, data=None): + @classmethod + def get_options(cls, contexts): + return [ + EnumDef("mtldup", + cls.mtl_dup_enum, + default=cls.mtl_dup_default, + label="Material Duplicate Options") + ] + + def load(self, context, name=None, namespace=None, options=None): from pymxs import runtime as rt + mat_dup_options = options.get("mtldup", self.mtl_dup_default) path = self.filepath_from_context(context) path = os.path.normpath(path) # import the max scene by using "merge file" path = path.replace('\\', '/') - rt.MergeMaxFile(path, quiet=True, includeFullGroup=True) + rt.MergeMaxFile(path, rt.Name(mat_dup_options), + quiet=True, includeFullGroup=True) max_objects = rt.getLastMergedNodes() max_object_names = [obj.name for obj in max_objects] # implement the OP/AYON custom attributes before load From 4092d177f56a48e6cb5f79e690a341f83d16ca53 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Mar 2024 23:21:15 +0800 Subject: [PATCH 09/39] QtWidget for setting the material options when updating --- .../hosts/max/plugins/load/load_max_scene.py | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 915abc75a1..9daf9bb72d 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -1,5 +1,5 @@ import os - +from qtpy import QtWidgets, QtCore from ayon_core.lib import EnumDef from ayon_core.hosts.max.api import lib from ayon_core.hosts.max.api.lib import ( @@ -13,6 +13,47 @@ from ayon_core.hosts.max.api.pipeline import ( remove_container_data ) from ayon_core.pipeline import get_representation_path, load +from ayon_core.settings import get_project_settings + + +class MaterialDupOptionsWindow(QtWidgets.QDialog): + + def __init__(self, material_options): + super(MaterialDupOptionsWindow, self).__init__() + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + + self.material_option = None + + self.widgets = { + "label": QtWidgets.QLabel( + "Select material duplicate options before loading the max scene."), + "material_options_list": QtWidgets.QListWidget(), + "warning": QtWidgets.QLabel("No material options selected!"), + "okButton": QtWidgets.QPushButton("Ok"), + } + for option in material_options: + self.widgets["material_options_list"].addItem(option) + # Build buttons. + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout.addWidget(self.widgets["okButton"]) + # Build layout. + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.widgets["label"]) + layout.addWidget(self.widgets["list"]) + layout.addWidget(self.widgets["buttons"]) + + self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + self.widgets["material_options_list"].itemPressed.connect( + self.on_material_optionsPressed) + + def on_material_optionsPressed(self, item): + self.material_option = item.text() + + def on_ok_pressed(self): + if self.material_option is None: + self.widgets["warning"].setVisible(True) + return + self.close() class MaxSceneLoader(load.LoaderPlugin): @@ -82,7 +123,9 @@ class MaxSceneLoader(load.LoaderPlugin): for prev_max_obj in prev_max_objects: if rt.isValidNode(prev_max_obj): # noqa rt.Delete(prev_max_obj) - rt.MergeMaxFile(path, quiet=True) + window = MaterialDupOptionsWindow(self.mtl_dup_enum) + window.exec_() + rt.MergeMaxFile(path, rt.Name(window.material_option), quiet=True) current_max_objects = rt.getLastMergedNodes() From 142206c71b75303143a969c5fd58e5d8f9718fa3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 9 Mar 2024 00:01:37 +0800 Subject: [PATCH 10/39] make sure Pop up window works --- .../ayon_core/hosts/max/plugins/load/load_max_scene.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 9daf9bb72d..f30d214cbe 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -13,7 +13,6 @@ from ayon_core.hosts.max.api.pipeline import ( remove_container_data ) from ayon_core.pipeline import get_representation_path, load -from ayon_core.settings import get_project_settings class MaterialDupOptionsWindow(QtWidgets.QDialog): @@ -29,20 +28,24 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): "Select material duplicate options before loading the max scene."), "material_options_list": QtWidgets.QListWidget(), "warning": QtWidgets.QLabel("No material options selected!"), + "buttons": QtWidgets.QWidget(), "okButton": QtWidgets.QPushButton("Ok"), + "cancelButton": QtWidgets.QPushButton("Cancel") } for option in material_options: self.widgets["material_options_list"].addItem(option) # Build buttons. layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) layout.addWidget(self.widgets["okButton"]) + layout.addWidget(self.widgets["cancelButton"]) # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["list"]) + layout.addWidget(self.widgets["material_options_list"]) layout.addWidget(self.widgets["buttons"]) self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) self.widgets["material_options_list"].itemPressed.connect( self.on_material_optionsPressed) @@ -55,6 +58,9 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): return self.close() + def on_cancel_pressed(self): + self.material_option = "promptMtlDups" + self.close() class MaxSceneLoader(load.LoaderPlugin): """Max Scene Loader.""" From a35db9aaebeba9644b8c0f6dac21ea90a67abb20 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 8 Mar 2024 22:21:41 +0100 Subject: [PATCH 11/39] Add option to compress .blend exports - fix #156 --- .../blender/plugins/publish/extract_blend.py | 5 ++++- .../publish/extract_blend_animation.py | 5 ++++- .../server/settings/publish_plugins.py | 19 +++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_blend.py b/client/ayon_core/hosts/blender/plugins/publish/extract_blend.py index dd2e33df80..731e91ad76 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_blend.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_blend.py @@ -13,6 +13,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): families = ["model", "camera", "rig", "action", "layout", "blendScene"] optional = True + # From settings + compress = False + def process(self, instance): if not self.is_active(instance.data): return @@ -53,7 +56,7 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin): if node.image and node.image.packed_file is None: node.image.pack() - bpy.data.libraries.write(filepath, data_blocks) + bpy.data.libraries.write(filepath, data_blocks, compress=self.compress) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_blend_animation.py b/client/ayon_core/hosts/blender/plugins/publish/extract_blend_animation.py index da663b46ea..64009c4b6c 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_blend_animation.py @@ -16,6 +16,9 @@ class ExtractBlendAnimation( families = ["animation"] optional = True + # From settings + compress = False + def process(self, instance): if not self.is_active(instance.data): return @@ -46,7 +49,7 @@ class ExtractBlendAnimation( data_blocks.add(child.animation_data.action) data_blocks.add(obj) - bpy.data.libraries.write(filepath, data_blocks) + bpy.data.libraries.write(filepath, data_blocks, compress=self.compress) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py index c2a989dd55..79c489d080 100644 --- a/server_addon/blender/server/settings/publish_plugins.py +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -44,6 +44,14 @@ class ExtractBlendModel(BaseSettingsModel): default_factory=list, title="Families" ) + compress: bool = SettingsField(True, title="Compress") + + +class ExtractBlendAnimationModel(BaseSettingsModel): + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + compress: bool = SettingsField(False, title="Compress") class ExtractPlayblastModel(BaseSettingsModel): @@ -51,6 +59,7 @@ class ExtractPlayblastModel(BaseSettingsModel): optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") presets: str = SettingsField("", title="Presets", widget="textarea") + compress: bool = SettingsField(False, title="Compress") @validator("presets") def validate_json(cls, value): @@ -110,8 +119,8 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=ValidatePluginModel, title="Extract ABC" ) - ExtractBlendAnimation: ValidatePluginModel = SettingsField( - default_factory=ValidatePluginModel, + ExtractBlendAnimation: ExtractBlendAnimationModel = SettingsField( + default_factory=ExtractBlendAnimationModel, title="Extract Blend Animation" ) ExtractAnimationFBX: ValidatePluginModel = SettingsField( @@ -198,7 +207,8 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { "action", "layout", "blendScene" - ] + ], + "compress": False }, "ExtractFBX": { "enabled": False, @@ -213,7 +223,8 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = { "ExtractBlendAnimation": { "enabled": True, "optional": True, - "active": True + "active": True, + "compress": False }, "ExtractAnimationFBX": { "enabled": False, From 485b669389728e6c3a18a7ee15c6827408012f5d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 8 Mar 2024 22:22:05 +0100 Subject: [PATCH 12/39] Bump blender addon version --- server_addon/blender/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/blender/server/version.py +++ b/server_addon/blender/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" From b3048277e2d34054c1420c435c0800d8276d69ee Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 9 Mar 2024 22:18:52 +0800 Subject: [PATCH 13/39] make sure the code is also running when it is at headless mode --- .../hosts/max/plugins/load/load_max_scene.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index f30d214cbe..3e8a918424 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -5,7 +5,8 @@ from ayon_core.hosts.max.api import lib from ayon_core.hosts.max.api.lib import ( unique_namespace, get_namespace, - object_transform_set + object_transform_set, + is_headless ) from ayon_core.hosts.max.api.pipeline import ( containerise, get_previous_loaded_object, @@ -129,9 +130,12 @@ class MaxSceneLoader(load.LoaderPlugin): for prev_max_obj in prev_max_objects: if rt.isValidNode(prev_max_obj): # noqa rt.Delete(prev_max_obj) - window = MaterialDupOptionsWindow(self.mtl_dup_enum) - window.exec_() - rt.MergeMaxFile(path, rt.Name(window.material_option), quiet=True) + material_option = self.mtl_dup_default + if not is_headless(): + window = MaterialDupOptionsWindow(self.mtl_dup_enum) + window.exec_() + material_option = window.material_option + rt.MergeMaxFile(path, rt.Name(material_option), quiet=True) current_max_objects = rt.getLastMergedNodes() From 98ee8a0486ab85bbb1232ba73d1b96136ee67652 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Feb 2024 13:34:36 +0100 Subject: [PATCH 14/39] Add Validate Instance In Context validator in Houdini --- .../publish/validate_instance_in_context.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..ac31254228 --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +"""Validate if instance asset is the same as context asset.""" + +import pyblish.api +from ayon_core.hosts.houdini.api.action import SelectROPAction +from ayon_core.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) + + +class ValidateInstanceInContextHoudini(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validator to check if instance asset match context asset. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + """ + # Similar to maya-equivalent `ValidateInstanceInContext` + + order = ValidateContentsOrder + label = "Instance in same Context" + optional = True + hosts = ["houdini"] + actions = [SelectROPAction, RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + + folderPath = instance.data.get("folderPath") + task = instance.data.get("task") + context = self.get_context(instance) + if (folderPath, task) != context: + context_label = "{} > {}".format(*context) + instance_label = "{} > {}".format(folderPath, task) + + raise PublishValidationError( + message=( + "Instance '{}' publishes to different asset than current " + "context: {}. Current context: {}".format( + instance.name, instance_label, context_label + ) + ), + description=( + "## Publishing to a different asset\n" + "There are publish instances present which are publishing " + "into a different asset than your current context.\n\n" + "Usually this is not what you want but there can be cases " + "where you might want to publish into another asset or " + "shot. If that's the case you can disable the validation " + "on the instance to ignore it." + ) + ) + + @classmethod + def repair(cls, instance): + context_asset, context_task = cls.get_context(instance) + + create_context = instance.context.data["create_context"] + instance_id = instance.data.get("instance_id") + created_instance = create_context.get_instance_by_id( + instance_id + ) + created_instance["folderPath"] = context_asset + created_instance["task"] = context_task + create_context.save_changes() + + @staticmethod + def get_context(instance): + """Return folderPath, task from publishing context data""" + context = instance.context + return context.data["folderPath"], context.data["task"] From a544ed4c4eadb561f0fd66538d60ce9427b5e2e0 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 11 Mar 2024 12:46:24 +0200 Subject: [PATCH 15/39] Add ValidateInstanceInContext settings --- server_addon/houdini/server/settings/publish.py | 8 ++++++++ server_addon/houdini/server/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index 1741568d63..8e0e7f7795 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -53,6 +53,9 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Latest Containers.", section="Validators") + ValidateInstanceInContextHoudini: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Instance is in same Context.") ValidateMeshIsStatic: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh is Static.") @@ -84,6 +87,11 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "optional": True, "active": True }, + "ValidateInstanceInContextHoudini": { + "enabled": True, + "optional": True, + "active": True + }, "ValidateMeshIsStatic": { "enabled": True, "optional": True, diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 5635676f6b..b5c9b6cb71 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.11" +__version__ = "0.2.12" From 1d45203491facaaad89ec3033e8d61966f964f49 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 11 Mar 2024 16:21:18 +0200 Subject: [PATCH 16/39] Update variables names - use squeare brackets instead of .get to retrieve 'instance_id' --- .../plugins/publish/validate_instance_in_context.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py index ac31254228..26708e306b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_instance_in_context.py @@ -31,12 +31,12 @@ class ValidateInstanceInContextHoudini(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return - folderPath = instance.data.get("folderPath") + folder_path = instance.data.get("folderPath") task = instance.data.get("task") context = self.get_context(instance) - if (folderPath, task) != context: + if (folder_path, task) != context: context_label = "{} > {}".format(*context) - instance_label = "{} > {}".format(folderPath, task) + instance_label = "{} > {}".format(folder_path, task) raise PublishValidationError( message=( @@ -58,14 +58,14 @@ class ValidateInstanceInContextHoudini(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - context_asset, context_task = cls.get_context(instance) + context_folder, context_task = cls.get_context(instance) create_context = instance.context.data["create_context"] - instance_id = instance.data.get("instance_id") + instance_id = instance.data["instance_id"] created_instance = create_context.get_instance_by_id( instance_id ) - created_instance["folderPath"] = context_asset + created_instance["folderPath"] = context_folder created_instance["task"] = context_task create_context.save_changes() From 88cd493f2df99b07582e397b102bb0c11a7ba6ac Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 19:19:51 +0800 Subject: [PATCH 17/39] fix the bug in regards to the non-existence of instance.context.data['subset'] --- .../ayon_core/hosts/max/plugins/publish/validate_model_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index a0cad4e454..d33b1af3ed 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -104,7 +104,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, compare = { "project": instance.context.data["projectName"], "asset": instance.context.data["folderPath"], - "subset": instance.context.data["subset"], + "subset": instance.name, } for key, required_value in compare.items(): if key in regex.groupindex: From d5bdcf2b3c5661108bf222f7e6158cc73738bd93 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 19:49:36 +0800 Subject: [PATCH 18/39] use instance.data['productName'] instead of instance.name --- .../ayon_core/hosts/max/plugins/publish/validate_model_name.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index d33b1af3ed..55b0908f29 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -100,11 +100,12 @@ class ValidateModelName(pyblish.api.InstancePlugin, return obj # Validate regex groups + cls.log.debug(instance.data["productName"]) invalid = False compare = { "project": instance.context.data["projectName"], "asset": instance.context.data["folderPath"], - "subset": instance.name, + "subset": instance.data["productName"], } for key, required_value in compare.items(): if key in regex.groupindex: From 1db9fbfe4056deca2da3238172e8328f28acc7b6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 19:51:32 +0800 Subject: [PATCH 19/39] clean up unnecessary msg --- .../ayon_core/hosts/max/plugins/publish/validate_model_name.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index 55b0908f29..903a31d54a 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -100,12 +100,11 @@ class ValidateModelName(pyblish.api.InstancePlugin, return obj # Validate regex groups - cls.log.debug(instance.data["productName"]) invalid = False compare = { "project": instance.context.data["projectName"], "asset": instance.context.data["folderPath"], - "subset": instance.data["productName"], + "subset": instance.data["productName"] } for key, required_value in compare.items(): if key in regex.groupindex: From 40e41c56ae6b70abcb30a958799539c2110fbf6e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 20:32:47 +0800 Subject: [PATCH 20/39] use instance.data['folderPath'] --- .../ayon_core/hosts/max/plugins/publish/validate_model_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index 903a31d54a..eb86e2e5bd 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -103,7 +103,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, invalid = False compare = { "project": instance.context.data["projectName"], - "asset": instance.context.data["folderPath"], + "asset": instance.data["folderPath"], "subset": instance.data["productName"] } for key, required_value in compare.items(): From 5137eabaafc480b3fafe52a3ec0cdc9a690896c8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 22:54:57 +0800 Subject: [PATCH 21/39] update the options to be more artist-friendly --- .../hosts/max/plugins/load/load_max_scene.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 3e8a918424..beb465a6ef 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -1,6 +1,6 @@ import os from qtpy import QtWidgets, QtCore -from ayon_core.lib import EnumDef +from ayon_core.lib.attribute_definitions import EnumDef from ayon_core.hosts.max.api import lib from ayon_core.hosts.max.api.lib import ( unique_namespace, @@ -23,6 +23,7 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) self.material_option = None + self.material_options = material_options self.widgets = { "label": QtWidgets.QLabel( @@ -33,7 +34,7 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): "okButton": QtWidgets.QPushButton("Ok"), "cancelButton": QtWidgets.QPushButton("Cancel") } - for option in material_options: + for option in material_options.values(): self.widgets["material_options_list"].addItem(option) # Build buttons. layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) @@ -51,7 +52,9 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): self.on_material_optionsPressed) def on_material_optionsPressed(self, item): - self.material_option = item.text() + self.material_option = next((key for key in self.material_options.keys() + if self.material_options[key]== item.text()), + None) def on_ok_pressed(self): if self.material_option is None: @@ -75,14 +78,17 @@ class MaxSceneLoader(load.LoaderPlugin): icon = "code-fork" color = "green" mtl_dup_default = "promptMtlDups" - mtl_dup_enum = ["promptMtlDups", "useMergedMtlDups", - "useSceneMtlDups", "renameMtlDups"] - + mtl_dup_enum_dict = { + "promptMtlDups": "Prompt Material", + "useMergedMtlDups": "Use Incoming Material", + "useSceneMtlDups": "Use Scene Material", + "renameMtlDups": "Merge and Rename Incoming Material" + } @classmethod def get_options(cls, contexts): return [ EnumDef("mtldup", - cls.mtl_dup_enum, + items=cls.mtl_dup_enum_dict, default=cls.mtl_dup_default, label="Material Duplicate Options") ] @@ -132,7 +138,7 @@ class MaxSceneLoader(load.LoaderPlugin): rt.Delete(prev_max_obj) material_option = self.mtl_dup_default if not is_headless(): - window = MaterialDupOptionsWindow(self.mtl_dup_enum) + window = MaterialDupOptionsWindow(self.mtl_dup_enum_dict) window.exec_() material_option = window.material_option rt.MergeMaxFile(path, rt.Name(material_option), quiet=True) From 68a95078a87b9d0d3c2dcd139b78f1b38bd28aa1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Mar 2024 23:10:07 +0800 Subject: [PATCH 22/39] rename prompt materials as Prompt on Duplicate Materials --- client/ayon_core/hosts/max/plugins/load/load_max_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index beb465a6ef..5c55455d00 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -79,7 +79,7 @@ class MaxSceneLoader(load.LoaderPlugin): color = "green" mtl_dup_default = "promptMtlDups" mtl_dup_enum_dict = { - "promptMtlDups": "Prompt Material", + "promptMtlDups": "Prompt on Duplicate Materials", "useMergedMtlDups": "Use Incoming Material", "useSceneMtlDups": "Use Scene Material", "renameMtlDups": "Merge and Rename Incoming Material" From 5f86e9eabaee04fd4e7e75e56d6385aeda19c05d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Mar 2024 16:12:12 +0100 Subject: [PATCH 23/39] copy and open of published workfile should work --- client/ayon_core/tools/workfiles/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index 86c6a62a11..6793cb23a9 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -573,6 +573,7 @@ class BaseWorkfileController( workdir, filename, template_key, + src_filepath=representation_filepath ) except Exception: failed = True From 4c9280b3a7161f9d26fc0725db6f8cb51f93640e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Mar 2024 14:08:55 +0800 Subject: [PATCH 24/39] add docstring to describe the material pop-up dialog & some code tweaks --- .../hosts/max/plugins/load/load_max_scene.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 5c55455d00..9707297c2f 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -17,7 +17,10 @@ from ayon_core.pipeline import get_representation_path, load class MaterialDupOptionsWindow(QtWidgets.QDialog): - + """The pop-up dialog allows users to choose material + duplicate options for importing Max objects when updating + or switching assets. + """ def __init__(self, material_options): super(MaterialDupOptionsWindow, self).__init__() self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) @@ -34,8 +37,10 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): "okButton": QtWidgets.QPushButton("Ok"), "cancelButton": QtWidgets.QPushButton("Cancel") } - for option in material_options.values(): - self.widgets["material_options_list"].addItem(option) + for key, value in material_options.items(): + item = QtWidgets.QListWidgetItem(value) + self.widgets["material_options_list"].addItem(item) + item.setData(QtCore.Qt.UserRole, key) # Build buttons. layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) layout.addWidget(self.widgets["okButton"]) @@ -49,12 +54,10 @@ class MaterialDupOptionsWindow(QtWidgets.QDialog): self.widgets["okButton"].pressed.connect(self.on_ok_pressed) self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) self.widgets["material_options_list"].itemPressed.connect( - self.on_material_optionsPressed) + self.on_material_options_pressed) - def on_material_optionsPressed(self, item): - self.material_option = next((key for key in self.material_options.keys() - if self.material_options[key]== item.text()), - None) + def on_material_options_pressed(self, item): + self.material_option = item.data(QtCore.Qt.UserRole) def on_ok_pressed(self): if self.material_option is None: From aa5566241e140bf270a2b436759b85d99f370ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 13 Mar 2024 10:13:20 +0100 Subject: [PATCH 25/39] :package: update unreal integration submodule --- client/ayon_core/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/integration b/client/ayon_core/hosts/unreal/integration index 6d2793170e..04b35dbf5f 160000 --- a/client/ayon_core/hosts/unreal/integration +++ b/client/ayon_core/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit 6d2793170ed57187842f683a943593973abcc337 +Subproject commit 04b35dbf5fc42d905281fc30d3a22b139c1855e5 From 30b854128ebaed681d7a84a2da014327cbc418d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Mar 2024 12:29:47 +0100 Subject: [PATCH 26/39] change scrollbar stylesheet to avoid issues --- client/ayon_core/style/style.css | 32 +++++++++++++++++++ .../tools/publisher/widgets/report_page.py | 2 ++ 2 files changed, 34 insertions(+) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index fcc76b0bff..607fd1fa31 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1067,6 +1067,38 @@ PixmapButton:disabled { font-size: 13pt; } +#PublisherVerticalScrollArea QScrollBar { + background: transparent; + margin: 0; + border: none; +} + +#PublisherVerticalScrollArea QScrollBar:horizontal { + height: 10px; + margin: 0; +} + +#PublisherVerticalScrollArea QScrollBar:vertical { + width: 10px; + margin: 0; +} + +#PublisherVerticalScrollArea QScrollBar::handle { + background: {color:bg-scroll-handle}; + border-radius: 4px; + margin: 1px; +} + +#PublisherVerticalScrollArea QScrollBar::handle:horizontal { + min-width: 20px; + min-height: 8px; +} + +#PublisherVerticalScrollArea QScrollBar::handle:vertical { + min-height: 20px; + min-width: 8px; +} + ValidationArtistMessage QLabel { font-size: 20pt; font-weight: bold; diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 1bbe8033f9..9d16fb67b2 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -56,6 +56,8 @@ class VerticalScrollArea(QtWidgets.QScrollArea): self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.setLayoutDirection(QtCore.Qt.RightToLeft) + self.setObjectName("PublisherVerticalScrollArea") + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # Background of scrollbar will be transparent scrollbar_bg = self.verticalScrollBar().parent() From 0b7efa19fe44845b3d1ec6628d6f8ff23903b2cb Mon Sep 17 00:00:00 2001 From: ChunYou Date: Wed, 13 Mar 2024 13:05:38 +0000 Subject: [PATCH 27/39] Maximise viewport instead of changing layout. Adding validator for extended viewport --- .../hosts/max/api/preview_animation.py | 25 +++++++++++-------- .../publish/validate_extended_viewport.py | 21 ++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py diff --git a/client/ayon_core/hosts/max/api/preview_animation.py b/client/ayon_core/hosts/max/api/preview_animation.py index f715efa53d..3872c4e493 100644 --- a/client/ayon_core/hosts/max/api/preview_animation.py +++ b/client/ayon_core/hosts/max/api/preview_animation.py @@ -31,23 +31,26 @@ def viewport_layout_and_camera(camera, layout="layout_1"): layout (str): layout to use in viewport, defaults to `layout_1` Use None to not change viewport layout during context. """ + needs_maximise = 0 + # Set to first active non extended viewport + rt.viewport.activeViewportEx(1) original_camera = rt.viewport.getCamera() - original_layout = rt.viewport.getLayout() - if not original_camera: - # if there is no original camera - # use the current camera as original - original_camera = rt.getNodeByName(camera) + original_type = rt.viewport.getType() review_camera = rt.getNodeByName(camera) + try: - if layout is not None: - layout = rt.Name(layout) - if rt.viewport.getLayout() != layout: - rt.viewport.setLayout(layout) + if rt.viewport.getLayout()!=rt.name(layout): + rt.execute("max tool maximize") + needs_maximise = 1 rt.viewport.setCamera(review_camera) yield finally: - rt.viewport.setLayout(original_layout) - rt.viewport.setCamera(original_camera) + if needs_maximise == 1: + rt.execute("max tool maximize") + if original_type == rt.Name("view_camera"): + rt.viewport.setCamera(original_camera) + else: + rt.viewport.setType(original_type) @contextlib.contextmanager diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py new file mode 100644 index 0000000000..96686970c6 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from ayon_core.pipeline import PublishValidationError +from pymxs import runtime as rt + + +class ValidateExtendedViewport(pyblish.api.InstancePlugin): + """Validate if the first viewport is an extended viewport.""" + + order = pyblish.api.ValidatorOrder + families = ["review"] + hosts = ["max"] + label = "Validate Extended Viewport" + + def process(self, instance): + try: + rt.viewport.activeViewportEx(1) + except RuntimeError: + raise PublishValidationError( + "Please make sure one viewport is not an extended viewport", title=self.label) + From 242b38b3d9b55e08537db75d07b55195b7efce24 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Mar 2024 14:38:24 +0100 Subject: [PATCH 28/39] add margin to left to offset content from scroll bar --- client/ayon_core/tools/publisher/widgets/report_page.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 9d16fb67b2..7475b39f52 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -502,7 +502,9 @@ class ValidationErrorsView(QtWidgets.QWidget): errors_scroll.setWidget(errors_widget) errors_layout = QtWidgets.QVBoxLayout(errors_widget) - errors_layout.setContentsMargins(0, 0, 0, 0) + # Add 5 margin to left so the is not directly on the edge of the + # scroll widget + errors_layout.setContentsMargins(5, 0, 0, 0) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(errors_scroll, 1) From 5d946ad2e5cf46391dd65cc8b06eafa270e645cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Mar 2024 15:14:02 +0100 Subject: [PATCH 29/39] removed 'requests_get' and 'requests_post' from lib functions --- client/ayon_core/lib/__init__.py | 9 ------- client/ayon_core/lib/connections.py | 38 ----------------------------- 2 files changed, 47 deletions(-) delete mode 100644 client/ayon_core/lib/connections.py diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index ab6a604adc..acd960d9b5 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -161,12 +161,6 @@ from .ayon_info import ( is_in_tests, ) - -from .connections import ( - requests_get, - requests_post -) - terminal = Terminal __all__ = [ @@ -283,7 +277,4 @@ __all__ = [ "is_staging_enabled", "is_dev_mode_enabled", "is_in_tests", - - "requests_get", - "requests_post" ] diff --git a/client/ayon_core/lib/connections.py b/client/ayon_core/lib/connections.py deleted file mode 100644 index 6a0cf4ae1c..0000000000 --- a/client/ayon_core/lib/connections.py +++ /dev/null @@ -1,38 +0,0 @@ -import requests -import os - - -def requests_post(*args, **kwargs): - """Wrap request post method. - - Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline server is - running with self-signed certificates and its certificate is not - added to trusted certificates on client machines. - - Warning: - Disabling SSL certificate validation is defeating one line - of defense SSL is providing, and it is not recommended. - - """ - if "verify" not in kwargs: - kwargs["verify"] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) - return requests.post(*args, **kwargs) - - -def requests_get(*args, **kwargs): - """Wrap request get method. - - Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline server is - running with self-signed certificates and its certificate is not - added to trusted certificates on client machines. - - Warning: - Disabling SSL certificate validation is defeating one line - of defense SSL is providing, and it is not recommended. - - """ - if "verify" not in kwargs: - kwargs["verify"] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) - return requests.get(*args, **kwargs) From 72d4cdd629498885a78742dce8f82cae53487e57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Mar 2024 15:14:14 +0100 Subject: [PATCH 30/39] remove it's usage from deadline addon --- client/ayon_core/modules/deadline/deadline_module.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index 97d346c287..d2f0e263d4 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -1,9 +1,10 @@ import os -import requests -import six import sys -from ayon_core.lib import requests_get, Logger +import requests +import six + +from ayon_core.lib import Logger from ayon_core.modules import AYONAddon, IPluginPaths @@ -56,6 +57,8 @@ class DeadlineModule(AYONAddon, IPluginPaths): RuntimeError: If deadline webservice is unreachable. """ + from .abstract_submit_deadline import requests_get + if not log: log = Logger.get_logger(__name__) From 85a022276f4d06867c6415393fd62603a813339d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Mar 2024 15:14:22 +0100 Subject: [PATCH 31/39] added todo comment --- client/ayon_core/modules/deadline/abstract_submit_deadline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index b2da4d1398..2e0518ae20 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -29,6 +29,10 @@ from ayon_core.pipeline.publish.lib import ( JSONDecodeError = getattr(json.decoder, "JSONDecodeError", ValueError) +# TODO both 'requests_post' and 'requests_get' should not set 'verify' based +# on environment variable. This should be done in a more controlled way, +# e.g. each deadline url could have checkbox to enabled/disable +# ssl verification. def requests_post(*args, **kwargs): """Wrap request post method. From fc85f469f7884852f7d1036c61c14a375c80a1aa Mon Sep 17 00:00:00 2001 From: ChunYou Date: Thu, 14 Mar 2024 02:17:05 +0000 Subject: [PATCH 32/39] Update Publish Validation Error Description --- .../hosts/max/plugins/publish/validate_extended_viewport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py index 96686970c6..7d9e3dc6fe 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py @@ -17,5 +17,8 @@ class ValidateExtendedViewport(pyblish.api.InstancePlugin): rt.viewport.activeViewportEx(1) except RuntimeError: raise PublishValidationError( - "Please make sure one viewport is not an extended viewport", title=self.label) + "Please make sure at least one viewport is not an extended viewport but a 3dsmax supported viewport " + "i.e camera/persp/orthographic view. To rectify it, please go to view in the top menubar, " + "go to Views -> Viewports Configuration -> layout and right click on one of the panels to change " + "it.", title=self.label) From 4944f956700704d2ac969fd40a3a9521d5815fcd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Mar 2024 19:23:49 +0800 Subject: [PATCH 33/39] check if there is the name endswith 'Main' in the renderlayer --- .../ayon_core/hosts/maya/plugins/publish/collect_inputs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py index d0b1029a03..6dff64681f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py @@ -180,6 +180,11 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): return copy.deepcopy(scene_containers) else: # Get the members of the layer + renderlayer = next((i for i in cmds.ls(type='renderLayer') + if i.endswith(renderlayer)), None) + if renderlayer is None: + return copy.deepcopy(scene_containers) + members = cmds.editRenderLayerMembers(renderlayer, query=True, fullNames=True) or [] From eb981d83c2a2df8a537ea29479c0439a700b5076 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Mar 2024 19:49:51 +0800 Subject: [PATCH 34/39] use setMembers instead of renderlayer --- .../ayon_core/hosts/maya/plugins/publish/collect_inputs.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py index 6dff64681f..d084804e05 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_inputs.py @@ -172,7 +172,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): """Collects inputs from nodes in renderlayer, incl. shaders + camera""" # Get the renderlayer - renderlayer = instance.data.get("renderlayer") + renderlayer = instance.data.get("setMembers") if renderlayer == "defaultRenderLayer": # Assume all loaded containers in the scene are inputs @@ -180,11 +180,6 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): return copy.deepcopy(scene_containers) else: # Get the members of the layer - renderlayer = next((i for i in cmds.ls(type='renderLayer') - if i.endswith(renderlayer)), None) - if renderlayer is None: - return copy.deepcopy(scene_containers) - members = cmds.editRenderLayerMembers(renderlayer, query=True, fullNames=True) or [] From e8bfb903620d07f8a74b76db075384a48c5a106d Mon Sep 17 00:00:00 2001 From: ChunYou Date: Thu, 14 Mar 2024 11:54:54 +0000 Subject: [PATCH 35/39] Change Instance to Context --- .../hosts/max/plugins/publish/validate_extended_viewport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py index 7d9e3dc6fe..8c64508a8b 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py @@ -4,7 +4,7 @@ from ayon_core.pipeline import PublishValidationError from pymxs import runtime as rt -class ValidateExtendedViewport(pyblish.api.InstancePlugin): +class ValidateExtendedViewport(pyblish.api.ContextPlugin): """Validate if the first viewport is an extended viewport.""" order = pyblish.api.ValidatorOrder @@ -12,7 +12,7 @@ class ValidateExtendedViewport(pyblish.api.InstancePlugin): hosts = ["max"] label = "Validate Extended Viewport" - def process(self, instance): + def process(self, context): try: rt.viewport.activeViewportEx(1) except RuntimeError: From df83dfaf40fc5f87767e2645f96d05efbe7f97e8 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:08:37 +0000 Subject: [PATCH 36/39] Commit description and rectify line length Co-authored-by: Roy Nieterau --- .../plugins/publish/validate_extended_viewport.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py index 8c64508a8b..ed476ec874 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_extended_viewport.py @@ -17,8 +17,13 @@ class ValidateExtendedViewport(pyblish.api.ContextPlugin): rt.viewport.activeViewportEx(1) except RuntimeError: raise PublishValidationError( - "Please make sure at least one viewport is not an extended viewport but a 3dsmax supported viewport " - "i.e camera/persp/orthographic view. To rectify it, please go to view in the top menubar, " - "go to Views -> Viewports Configuration -> layout and right click on one of the panels to change " - "it.", title=self.label) + "Please make sure one viewport is not an extended viewport", + description = ( + "Please make sure at least one viewport is not an " + "extended viewport but a 3dsmax supported viewport " + "i.e camera/persp/orthographic view.\n\n" + "To rectify it, please go to view in the top menubar, " + "go to Views -> Viewports Configuration -> Layout and " + "right click on one of the panels to change it." + )) From 305c76b6016185fd770a00d587907bd88847e7c5 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:08:57 +0000 Subject: [PATCH 37/39] Add space for cosmetics Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/max/api/preview_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/api/preview_animation.py b/client/ayon_core/hosts/max/api/preview_animation.py index 3872c4e493..399d3b6222 100644 --- a/client/ayon_core/hosts/max/api/preview_animation.py +++ b/client/ayon_core/hosts/max/api/preview_animation.py @@ -39,7 +39,7 @@ def viewport_layout_and_camera(camera, layout="layout_1"): review_camera = rt.getNodeByName(camera) try: - if rt.viewport.getLayout()!=rt.name(layout): + if rt.viewport.getLayout() != rt.name(layout): rt.execute("max tool maximize") needs_maximise = 1 rt.viewport.setCamera(review_camera) From 25c5b7140b53472110a037f5725631f8f86073bf Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 15 Mar 2024 07:18:46 +0000 Subject: [PATCH 38/39] Update render_settings.py --- server_addon/maya/server/settings/render_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/render_settings.py b/server_addon/maya/server/settings/render_settings.py index 577049b42f..bc476ec49c 100644 --- a/server_addon/maya/server/settings/render_settings.py +++ b/server_addon/maya/server/settings/render_settings.py @@ -355,7 +355,7 @@ class RedshiftSettingsModel(BaseSettingsModel): ) additional_options: list[AdditionalOptionsModel] = SettingsField( default_factory=list, - title="Additional Vray Options", + title="Additional Redshift Options", description=( "Add additional options - put attribute and value, like " "reflectionMaxTraceDepth and 3" From 0f71a89f888ecbfa66e6b245c460830428184087 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 15 Mar 2024 21:31:05 +0800 Subject: [PATCH 39/39] make sure the scene file would be saved and increment before deadline submission --- .../max/plugins/publish/increment_workfile_version.py | 2 +- .../ayon_core/hosts/max/plugins/publish/save_scene.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/increment_workfile_version.py b/client/ayon_core/hosts/max/plugins/publish/increment_workfile_version.py index 5f319966fe..c7c3f49626 100644 --- a/client/ayon_core/hosts/max/plugins/publish/increment_workfile_version.py +++ b/client/ayon_core/hosts/max/plugins/publish/increment_workfile_version.py @@ -9,7 +9,7 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder + 0.9 label = "Increment Workfile Version" hosts = ["max"] - families = ["workfile"] + families = ["maxrender", "workfile"] def process(self, context): path = context.data["currentFile"] diff --git a/client/ayon_core/hosts/max/plugins/publish/save_scene.py b/client/ayon_core/hosts/max/plugins/publish/save_scene.py index 1c59335ceb..fe2c7f50f4 100644 --- a/client/ayon_core/hosts/max/plugins/publish/save_scene.py +++ b/client/ayon_core/hosts/max/plugins/publish/save_scene.py @@ -2,7 +2,7 @@ import pyblish.api from ayon_core.pipeline import registered_host -class SaveCurrentScene(pyblish.api.ContextPlugin): +class SaveCurrentScene(pyblish.api.InstancePlugin): """Save current scene""" label = "Save current file" @@ -10,13 +10,15 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): hosts = ["max"] families = ["maxrender", "workfile"] - def process(self, context): + def process(self, instance): host = registered_host() current_file = host.get_current_workfile() - assert context.data["currentFile"] == current_file + assert instance.context.data["currentFile"] == current_file + if instance.data["productType"] == "maxrender": + host.save_workfile(current_file) - if host.workfile_has_unsaved_changes(): + elif host.workfile_has_unsaved_changes(): self.log.info(f"Saving current file: {current_file}") host.save_workfile(current_file) else: