From b1f8a1c9b4649507b211715b44cad27cd1e7937f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:19:41 +0100 Subject: [PATCH 01/37] Fix argument name --- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py index eaa7ff1ae3..004b85f420 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -64,7 +64,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): path = self.filepath_from_context(context) self._set_path(grid_node, path=path, - representation=context["representation"]) + repre_entity=context["representation"]) # Lock the shape node so the user can't delete the transform/shape # as if it was referenced @@ -94,7 +94,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): assert len(grid_nodes) == 1, "This is a bug" # Update the VRayVolumeGrid - self._set_path(grid_nodes[0], path=path, representation=repre_entity) + self._set_path(grid_nodes[0], path=path, repre_entity=repre_entity) # Update container representation cmds.setAttr(container["objectName"] + ".representation", From 87a0c7555c823c29cc54af9cd8b747dc9b92e6a1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 00:35:41 +0100 Subject: [PATCH 02/37] Fix indentations --- .../plugins/publish/collect_karma_rop.py | 28 +++--- .../plugins/publish/collect_mantra_rop.py | 92 +++++++++---------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py index 85100bc2c6..78651b0c69 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -41,23 +41,23 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): instance.data["chunkSize"] = chunk_size self.log.debug("Chunk Size: %s" % chunk_size) - default_prefix = evalParmNoFrame(rop, "picture") - render_products = [] + default_prefix = evalParmNoFrame(rop, "picture") + render_products = [] - # Default beauty AOV - beauty_product = self.get_render_product_name( - prefix=default_prefix, suffix=None - ) - render_products.append(beauty_product) + # Default beauty AOV + beauty_product = self.get_render_product_name( + prefix=default_prefix, suffix=None + ) + render_products.append(beauty_product) - files_by_aov = { - "beauty": self.generate_expected_files(instance, - beauty_product) - } + files_by_aov = { + "beauty": self.generate_expected_files(instance, + beauty_product) + } - filenames = list(render_products) - instance.data["files"] = filenames - instance.data["renderProducts"] = colorspace.ARenderProduct() + filenames = list(render_products) + instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() for product in render_products: self.log.debug("Found render product: %s" % product) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py index d46476c2ce..df9acc4b61 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -41,57 +41,57 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): instance.data["chunkSize"] = chunk_size self.log.debug("Chunk Size: %s" % chunk_size) - default_prefix = evalParmNoFrame(rop, "vm_picture") - render_products = [] + default_prefix = evalParmNoFrame(rop, "vm_picture") + render_products = [] - # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("soho_outputmode").eval()) - instance.data["splitRender"] = split_render - export_prefix = None - export_products = [] - if split_render: - export_prefix = evalParmNoFrame( - rop, "soho_diskfile", pad_character="0" - ) - beauty_export_product = self.get_render_product_name( - prefix=export_prefix, - suffix=None) - export_products.append(beauty_export_product) - self.log.debug( - "Found export product: {}".format(beauty_export_product) - ) - instance.data["ifdFile"] = beauty_export_product - instance.data["exportFiles"] = list(export_products) - - # Default beauty AOV - beauty_product = self.get_render_product_name( - prefix=default_prefix, suffix=None + # Store whether we are splitting the render job (export + render) + split_render = bool(rop.parm("soho_outputmode").eval()) + instance.data["splitRender"] = split_render + export_prefix = None + export_products = [] + if split_render: + export_prefix = evalParmNoFrame( + rop, "soho_diskfile", pad_character="0" ) - render_products.append(beauty_product) + beauty_export_product = self.get_render_product_name( + prefix=export_prefix, + suffix=None) + export_products.append(beauty_export_product) + self.log.debug( + "Found export product: {}".format(beauty_export_product) + ) + instance.data["ifdFile"] = beauty_export_product + instance.data["exportFiles"] = list(export_products) - files_by_aov = { - "beauty": self.generate_expected_files(instance, - beauty_product) - } + # Default beauty AOV + beauty_product = self.get_render_product_name( + prefix=default_prefix, suffix=None + ) + render_products.append(beauty_product) - aov_numbers = rop.evalParm("vm_numaux") - if aov_numbers > 0: - # get the filenames of the AOVs - for i in range(1, aov_numbers + 1): - var = rop.evalParm("vm_variable_plane%d" % i) - if var: - aov_name = "vm_filename_plane%d" % i - aov_boolean = "vm_usefile_plane%d" % i - aov_enabled = rop.evalParm(aov_boolean) - has_aov_path = rop.evalParm(aov_name) - if has_aov_path and aov_enabled == 1: - aov_prefix = evalParmNoFrame(rop, aov_name) - aov_product = self.get_render_product_name( - prefix=aov_prefix, suffix=None - ) - render_products.append(aov_product) + files_by_aov = { + "beauty": self.generate_expected_files(instance, + beauty_product) + } - files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa + aov_numbers = rop.evalParm("vm_numaux") + if aov_numbers > 0: + # get the filenames of the AOVs + for i in range(1, aov_numbers + 1): + var = rop.evalParm("vm_variable_plane%d" % i) + if var: + aov_name = "vm_filename_plane%d" % i + aov_boolean = "vm_usefile_plane%d" % i + aov_enabled = rop.evalParm(aov_boolean) + has_aov_path = rop.evalParm(aov_name) + if has_aov_path and aov_enabled == 1: + aov_prefix = evalParmNoFrame(rop, aov_name) + aov_product = self.get_render_product_name( + prefix=aov_prefix, suffix=None + ) + render_products.append(aov_product) + + files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa for product in render_products: self.log.debug("Found render product: %s" % product) From 5496484aab7c538cadc3be013c1408bf9c730444 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 10:23:10 +0100 Subject: [PATCH 03/37] Fix settings title (typo) + add description --- server_addon/maya/server/settings/publishers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3a6de2eb44..fe5c10e93c 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -322,7 +322,9 @@ class ExtractCameraAlembicModel(BaseSettingsModel): optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") bake_attributes: str = SettingsField( - "[]", title="Base Attributes", widget="textarea" + "[]", title="Bake Attributes", widget="textarea", + description="List of attributes that will be included in the alembic " + "export.", ) @validator("bake_attributes") From 7f0098f92fae0d29c4808e17a00ed9f2233fa65c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 11:48:51 +0100 Subject: [PATCH 04/37] Houdini: Add generic filepath loader --- .../houdini/plugins/load/load_filepath.py | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 client/ayon_core/hosts/houdini/plugins/load/load_filepath.py diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py b/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py new file mode 100644 index 0000000000..515ffa6027 --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py @@ -0,0 +1,129 @@ +import os +import re + +from ayon_core.pipeline import load +from openpype.hosts.houdini.api import pipeline + +import hou + + +class FilePathLoader(load.LoaderPlugin): + """Load a managed filepath to a null node. + + This is useful if for a particular workflow there is no existing loader + yet. A Houdini artists can load as the generic filepath loader and then + reference the relevant Houdini parm to use the exact value. The benefit + is that this filepath will be managed and can be updated as usual. + + """ + + label = "Load filepath to node" + order = 9 + icon = "link" + color = "white" + product_types = {"*"} + representations = ["*"] + + def load(self, context, name=None, namespace=None, data=None): + + # Get the root node + obj = hou.node("/obj") + + # Define node name + namespace = namespace if namespace else context["folder"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create a null node + container = obj.createNode("null", node_name=node_name) + + # Destroy any children + for node in container.children(): + node.destroy() + + # Add filepath attribute, set value as default value + filepath = self.format_path( + path=self.filepath_from_context(context), + representation=context["representation"] + ) + parm_template_group = container.parmTemplateGroup() + attr_folder = hou.FolderParmTemplate("attributes_folder", "Attributes") + parm = hou.StringParmTemplate(name="filepath", + label="Filepath", + num_components=1, + default_value=(filepath,)) + attr_folder.addParmTemplate(parm) + parm_template_group.append(attr_folder) + + # Hide some default labels + for folder_label in ["Transform", "Render", "Misc", "Redshift OBJ"]: + folder = parm_template_group.findFolder(folder_label) + if not folder: + continue + parm_template_group.hideFolder(folder_label, True) + + container.setParmTemplateGroup(parm_template_group) + + container.setDisplayFlag(False) + container.setSelectableInViewport(False) + container.useXray(False) + + nodes = [container] + + self[:] = nodes + + return pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", + ) + + def update(self, container, context): + + # Update the file path + representation_entity = context["representation"] + file_path = self.format_path( + path=self.filepath_from_context(context), + representation=representation_entity + ) + + node = container["node"] + node.setParms({ + "filepath": file_path, + "representation": str(representation_entity["id"]) + }) + + # Update the parameter default value (cosmetics) + parm_template_group = node.parmTemplateGroup() + parm = parm_template_group.find("filepath") + parm.setDefaultValue((file_path,)) + parm_template_group.replace(parm_template_group.find("filepath"), + parm) + node.setParmTemplateGroup(parm_template_group) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + + node = container["node"] + node.destroy() + + @staticmethod + def format_path(path: str, representation: dict) -> str: + """Format file path for sequence with $F.""" + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) + + # The path is either a single file or sequence in a folder. + frame = representation["context"].get("frame") + if frame is not None: + # Substitute frame number in sequence with $F with padding + ext = representation.get("ext", representation["name"]) + token = "$F{}".format(len(frame)) # e.g. $F4 + pattern = r"\.(\d+)\.{ext}$".format(ext=re.escape(ext)) + path = re.sub(pattern, ".{}.{}".format(token, ext), path) + + return os.path.normpath(path).replace("\\", "/") From 3888ce8486bc4910557e8eab33484a227beb0206 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 11:52:38 +0100 Subject: [PATCH 05/37] Houdini: Implement `switch` method for Camera Loader --- client/ayon_core/hosts/houdini/plugins/load/load_camera.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py index 605e5724e6..c57b14043a 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -167,6 +167,9 @@ class CameraLoader(load.LoaderPlugin): temp_camera.destroy() + def switch(self, container, context): + self.update(container, context) + def remove(self, container): node = container["node"] From 4f3391eb8a7d99aa97ea527b176fad2299d0704d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 24 Mar 2024 11:52:58 +0100 Subject: [PATCH 06/37] Remove redundant commented print --- client/ayon_core/hosts/houdini/plugins/load/load_camera.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py index c57b14043a..7cb4542d0c 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -198,7 +198,6 @@ class CameraLoader(load.LoaderPlugin): def _match_maya_render_mask(self, camera): """Workaround to match Maya render mask in Houdini""" - # print("Setting match maya render mask ") parm = camera.parm("aperture") expression = parm.expression() expression = expression.replace("return ", "aperture = ") From e4c8cf2f7e5745a7d703f391c3b1537b90322c36 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 13:04:15 +0100 Subject: [PATCH 07/37] Resolve: Allow to minimize the menu --- client/ayon_core/hosts/resolve/api/menu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/resolve/api/menu.py b/client/ayon_core/hosts/resolve/api/menu.py index 59eba14d83..3324295615 100644 --- a/client/ayon_core/hosts/resolve/api/menu.py +++ b/client/ayon_core/hosts/resolve/api/menu.py @@ -48,6 +48,7 @@ class OpenPypeMenu(QtWidgets.QWidget): QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) From d2396140106182cc4b8de4a175fa2f6fc2d9cd43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 13:27:26 +0100 Subject: [PATCH 08/37] Refactor `OpenPype` to `AYON` --- client/ayon_core/hosts/resolve/api/menu.py | 10 +++++----- client/ayon_core/hosts/resolve/api/menu_style.qss | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/resolve/api/menu.py b/client/ayon_core/hosts/resolve/api/menu.py index 3324295615..7f3a669ecb 100644 --- a/client/ayon_core/hosts/resolve/api/menu.py +++ b/client/ayon_core/hosts/resolve/api/menu.py @@ -38,9 +38,9 @@ class Spacer(QtWidgets.QWidget): self.setLayout(layout) -class OpenPypeMenu(QtWidgets.QWidget): +class AYONMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): - super(OpenPypeMenu, self).__init__(*args, **kwargs) + super(AYONMenu, self).__init__(*args, **kwargs) self.setObjectName(f"{MENU_LABEL}Menu") @@ -174,11 +174,11 @@ class OpenPypeMenu(QtWidgets.QWidget): def launch_pype_menu(): app = QtWidgets.QApplication(sys.argv) - pype_menu = OpenPypeMenu() + ayon_menu = AYONMenu() stylesheet = load_stylesheet() - pype_menu.setStyleSheet(stylesheet) + ayon_menu.setStyleSheet(stylesheet) - pype_menu.show() + ayon_menu.show() sys.exit(app.exec_()) diff --git a/client/ayon_core/hosts/resolve/api/menu_style.qss b/client/ayon_core/hosts/resolve/api/menu_style.qss index 3d51c7139f..ad8932d881 100644 --- a/client/ayon_core/hosts/resolve/api/menu_style.qss +++ b/client/ayon_core/hosts/resolve/api/menu_style.qss @@ -51,7 +51,7 @@ QLineEdit { qproperty-alignment: AlignCenter; } -#OpenPypeMenu { +#AYONMenu { qproperty-alignment: AlignLeft; min-width: 10em; border: 1px solid #fef9ef; From 44ed77a9f655c03ed585596b08eafc2e6dd2a402 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 14:20:14 +0100 Subject: [PATCH 09/37] Houdini: Implement USD loader to SOPs via `usdimport` --- .../houdini/plugins/load/load_usd_sop.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py new file mode 100644 index 0000000000..b32210e399 --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py @@ -0,0 +1,91 @@ +import os + +from ayon_core.pipeline import load +from ayon_core.hosts.houdini.api import pipeline + + +class SopUsdImportLoader(load.LoaderPlugin): + """Load USD to SOPs via `usdimport`""" + + label = "Load USD to SOPs" + product_types = {"*"} + representations = ["usd"] + order = -6 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + import hou + + # Format file name, Houdini only wants forward slashes + file_path = self.filepath_from_context(context) + file_path = os.path.normpath(file_path) + file_path = file_path.replace("\\", "/") + + # Get the root node + obj = hou.node("/obj") + + # Define node name + namespace = namespace if namespace else context["folder"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create a new geo node + container = obj.createNode("geo", node_name=node_name) + + # Remove the file node, it only loads static meshes + # Houdini 17 has removed the file node from the geo node + file_node = container.node("file1") + if file_node: + file_node.destroy() + + # Create a usdimport node + usdimport = container.createNode("usdimport", node_name=node_name) + usdimport.setParms({"filepath1": file_path}) + + # Ensure display flag is on the first input node and not on the OUT + # node to optimize "debug" displaying in the viewport. + usdimport.setDisplayFlag(True) + + # Set new position for unpack node else it gets cluttered + nodes = [container, usdimport] + for nr, node in enumerate(nodes): + node.setPosition([0, (0 - nr)]) + + self[:] = nodes + + return pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", + ) + + def update(self, container, context): + + node = container["node"] + try: + usdimport_node = next( + n for n in node.children() if n.type().name() == "usdimport" + ) + except StopIteration: + self.log.error("Could not find node of type `usdimport`") + return + + # Update the file path + file_path = self.filepath_from_context(context) + file_path = file_path.replace("\\", "/") + + usdimport_node.setParms({"filepath1": file_path}) + + # Update attribute + node.setParms({"representation": context["representation"]["id"]}) + + def remove(self, container): + + node = container["node"] + node.destroy() + + def switch(self, container, representation): + self.update(container, representation) From 24d87bf92a8be56fe837013860c2bb944f7520f6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 14:22:27 +0100 Subject: [PATCH 10/37] Remove redundant logic --- .../hosts/houdini/plugins/load/load_usd_sop.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py index b32210e399..3607987e15 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py @@ -32,24 +32,12 @@ class SopUsdImportLoader(load.LoaderPlugin): # Create a new geo node container = obj.createNode("geo", node_name=node_name) - # Remove the file node, it only loads static meshes - # Houdini 17 has removed the file node from the geo node - file_node = container.node("file1") - if file_node: - file_node.destroy() - # Create a usdimport node usdimport = container.createNode("usdimport", node_name=node_name) usdimport.setParms({"filepath1": file_path}) - # Ensure display flag is on the first input node and not on the OUT - # node to optimize "debug" displaying in the viewport. - usdimport.setDisplayFlag(True) - # Set new position for unpack node else it gets cluttered nodes = [container, usdimport] - for nr, node in enumerate(nodes): - node.setPosition([0, (0 - nr)]) self[:] = nodes From 135dd15870feb230231ce888949f5a4c56ce8749 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 14:26:18 +0100 Subject: [PATCH 11/37] Remove redundant assignment to `self[:]` --- client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py index 3607987e15..5b7e022e73 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py @@ -39,8 +39,6 @@ class SopUsdImportLoader(load.LoaderPlugin): # Set new position for unpack node else it gets cluttered nodes = [container, usdimport] - self[:] = nodes - return pipeline.containerise( node_name, namespace, From 2b1e31001c74a6dd263236e0f7a5c0600c372249 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:52:11 +0100 Subject: [PATCH 12/37] duplicated non python host launch script for photoshop --- .../hosts/photoshop/api/launch_script.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 client/ayon_core/hosts/photoshop/api/launch_script.py diff --git a/client/ayon_core/hosts/photoshop/api/launch_script.py b/client/ayon_core/hosts/photoshop/api/launch_script.py new file mode 100644 index 0000000000..c036b63c46 --- /dev/null +++ b/client/ayon_core/hosts/photoshop/api/launch_script.py @@ -0,0 +1,93 @@ +"""Script wraps launch mechanism of Photoshop implementations. + +Arguments passed to the script are passed to launch function in host +implementation. In all cases requires host app executable and may contain +workfile or others. +""" + +import os +import sys + +from ayon_core.hosts.photoshop.api.lib import main + +# Get current file to locate start point of sys.argv +CURRENT_FILE = os.path.abspath(__file__) + + +def show_error_messagebox(title, message, detail_message=None): + """Function will show message and process ends after closing it.""" + from qtpy import QtWidgets, QtCore + from ayon_core import style + + app = QtWidgets.QApplication([]) + app.setStyleSheet(style.load_stylesheet()) + + msgbox = QtWidgets.QMessageBox() + msgbox.setWindowTitle(title) + msgbox.setText(message) + + if detail_message: + msgbox.setDetailedText(detail_message) + + msgbox.setWindowModality(QtCore.Qt.ApplicationModal) + msgbox.show() + + sys.exit(app.exec_()) + + +def on_invalid_args(script_not_found): + """Show to user message box saying that something went wrong. + + Tell user that arguments to launch implementation are invalid with + arguments details. + + Args: + script_not_found (bool): Use different message based on this value. + """ + + title = "Invalid arguments" + joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv) + if script_not_found: + submsg = "Where couldn't find script path:\n\"{}\"" + else: + submsg = "Expected Host executable after script path:\n\"{}\"" + + message = "BUG: Got invalid arguments so can't launch Host application." + detail_message = "Process was launched with arguments:\n{}\n\n{}".format( + joined_args, + submsg.format(CURRENT_FILE) + ) + + show_error_messagebox(title, message, detail_message) + + +def main(argv): + # Modify current file path to find match in sys.argv which may be different + # on windows (different letter cases and slashes). + modified_current_file = CURRENT_FILE.replace("\\", "/").lower() + + # Create a copy of sys argv + sys_args = list(argv) + after_script_idx = None + # Find script path in sys.argv to know index of argv where host + # executable should be. + for idx, item in enumerate(sys_args): + if item.replace("\\", "/").lower() == modified_current_file: + after_script_idx = idx + 1 + break + + # Validate that there is at least one argument after script path + launch_args = None + if after_script_idx is not None: + launch_args = sys_args[after_script_idx:] + + if launch_args: + # Launch host implementation + main(*launch_args) + else: + # Show message box + on_invalid_args(after_script_idx is None) + + +if __name__ == "__main__": + main(sys.argv) From fee6a9b4485de958af128c9db0f7da8f4dbfbfa6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:52:52 +0100 Subject: [PATCH 13/37] renamed 'PHOTOSHOP_HOST_DIR' to 'PHOTOSHOP_ADDON_ROOT' --- client/ayon_core/hosts/photoshop/__init__.py | 4 ++-- client/ayon_core/hosts/photoshop/addon.py | 2 +- client/ayon_core/hosts/photoshop/api/pipeline.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/photoshop/__init__.py b/client/ayon_core/hosts/photoshop/__init__.py index 773f73d624..0c79afaab8 100644 --- a/client/ayon_core/hosts/photoshop/__init__.py +++ b/client/ayon_core/hosts/photoshop/__init__.py @@ -1,10 +1,10 @@ from .addon import ( + PHOTOSHOP_ADDON_ROOT, PhotoshopAddon, - PHOTOSHOP_HOST_DIR, ) __all__ = ( + "PHOTOSHOP_ADDON_ROOT", "PhotoshopAddon", - "PHOTOSHOP_HOST_DIR", ) diff --git a/client/ayon_core/hosts/photoshop/addon.py b/client/ayon_core/hosts/photoshop/addon.py index 3016912960..28833121c4 100644 --- a/client/ayon_core/hosts/photoshop/addon.py +++ b/client/ayon_core/hosts/photoshop/addon.py @@ -1,7 +1,7 @@ import os from ayon_core.addon import AYONAddon, IHostAddon -PHOTOSHOP_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) +PHOTOSHOP_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__)) class PhotoshopAddon(AYONAddon, IHostAddon): diff --git a/client/ayon_core/hosts/photoshop/api/pipeline.py b/client/ayon_core/hosts/photoshop/api/pipeline.py index 32f66cf7fb..27cfa5a7b5 100644 --- a/client/ayon_core/hosts/photoshop/api/pipeline.py +++ b/client/ayon_core/hosts/photoshop/api/pipeline.py @@ -21,14 +21,14 @@ from ayon_core.host import ( ) from ayon_core.pipeline.load import any_outdated_containers -from ayon_core.hosts.photoshop import PHOTOSHOP_HOST_DIR +from ayon_core.hosts.photoshop import PHOTOSHOP_ADDON_ROOT from ayon_core.tools.utils import get_ayon_qt_app from . import lib log = Logger.get_logger(__name__) -PLUGINS_DIR = os.path.join(PHOTOSHOP_HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(PHOTOSHOP_ADDON_ROOT, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") From aaea1e14859799dd8c443c832dce2ffa75be8e3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:53:17 +0100 Subject: [PATCH 14/37] photoshop has own prelaunch hook to modify launch arguments --- .../hooks/pre_non_python_host_launch.py | 2 +- client/ayon_core/hosts/photoshop/__init__.py | 2 + client/ayon_core/hosts/photoshop/addon.py | 14 +++ .../hosts/photoshop/hooks/pre_launch_args.py | 95 +++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py diff --git a/client/ayon_core/hooks/pre_non_python_host_launch.py b/client/ayon_core/hooks/pre_non_python_host_launch.py index fed4c99447..3d3c24b308 100644 --- a/client/ayon_core/hooks/pre_non_python_host_launch.py +++ b/client/ayon_core/hooks/pre_non_python_host_launch.py @@ -17,7 +17,7 @@ class NonPythonHostHook(PreLaunchHook): python script which launch the host. For these cases it is necessary to prepend python (or ayon) executable and script path before application's. """ - app_groups = {"harmony", "photoshop", "aftereffects"} + app_groups = {"harmony", "aftereffects"} order = 20 launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hosts/photoshop/__init__.py b/client/ayon_core/hosts/photoshop/__init__.py index 0c79afaab8..cf21b7df75 100644 --- a/client/ayon_core/hosts/photoshop/__init__.py +++ b/client/ayon_core/hosts/photoshop/__init__.py @@ -1,10 +1,12 @@ from .addon import ( PHOTOSHOP_ADDON_ROOT, PhotoshopAddon, + get_launch_script_path, ) __all__ = ( "PHOTOSHOP_ADDON_ROOT", "PhotoshopAddon", + "get_launch_script_path", ) diff --git a/client/ayon_core/hosts/photoshop/addon.py b/client/ayon_core/hosts/photoshop/addon.py index 28833121c4..65fe6a7cd1 100644 --- a/client/ayon_core/hosts/photoshop/addon.py +++ b/client/ayon_core/hosts/photoshop/addon.py @@ -20,3 +20,17 @@ class PhotoshopAddon(AYONAddon, IHostAddon): def get_workfile_extensions(self): return [".psd", ".psb"] + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(PHOTOSHOP_ADDON_ROOT, "hooks") + ] + + +def get_launch_script_path(): + return os.path.join( + PHOTOSHOP_ADDON_ROOT, "api", "launch_script.py" + ) + diff --git a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py new file mode 100644 index 0000000000..0689a6c2f0 --- /dev/null +++ b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py @@ -0,0 +1,95 @@ +import os +import platform +import subprocess + +from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib.applications import ( + PreLaunchHook, + LaunchTypes, +) +from ayon_core.hosts.photoshop import get_launch_script_path + + +def get_launch_kwargs(kwargs): + """Explicit setting of kwargs for Popen for Photoshop. + + Expected behavior + - ayon_console opens window with logs + - ayon has stdout/stderr available for capturing + + Args: + kwargs (Union[dict, None]): Current kwargs or None. + + """ + if kwargs is None: + kwargs = {} + + if platform.system().lower() != "windows": + return kwargs + + executable_path = os.environ.get("AYON_EXECUTABLE") + + executable_filename = "" + if executable_path: + executable_filename = os.path.basename(executable_path) + + is_gui_executable = "ayon_console" not in executable_filename + if is_gui_executable: + kwargs.update({ + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL + }) + else: + kwargs.update({ + "creationflags": subprocess.CREATE_NEW_CONSOLE + }) + return kwargs + + +class PhotoshopPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and script path to Photoshop implementation + before Photoshop executable and add last workfile path to launch arguments. + + Existence of last workfile is checked. If workfile does not exists tries + to copy templated workfile from predefined path. + """ + app_groups = {"photoshop"} + + order = 20 + launch_types = {LaunchTypes.local} + + def execute(self): + # Pop executable + executable_path = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + script_path = get_launch_script_path() + + new_launch_args = get_ayon_launcher_args( + "run", script_path, executable_path + ) + # Add workfile path if exists + workfile_path = self.data["last_workfile_path"] + if ( + self.data.get("start_last_workfile") + and workfile_path + and os.path.exists(workfile_path) + ): + new_launch_args.append(workfile_path) + + # Append as whole list as these arguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.launch_context.launch_args.extend(remainders) + + self.launch_context.kwargs = get_launch_kwargs( + self.launch_context.kwargs + ) From 0d75102d8a255237b5d8fa180e13d0febe250c31 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:53:54 +0100 Subject: [PATCH 15/37] aftereffects have own launch script --- .../hosts/aftereffects/api/launch_script.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 client/ayon_core/hosts/aftereffects/api/launch_script.py diff --git a/client/ayon_core/hosts/aftereffects/api/launch_script.py b/client/ayon_core/hosts/aftereffects/api/launch_script.py new file mode 100644 index 0000000000..ad4e779bd0 --- /dev/null +++ b/client/ayon_core/hosts/aftereffects/api/launch_script.py @@ -0,0 +1,93 @@ +"""Script wraps launch mechanism of AfterEffects implementations. + +Arguments passed to the script are passed to launch function in host +implementation. In all cases requires host app executable and may contain +workfile or others. +""" + +import os +import sys + +from ayon_core.hosts.aftereffects.api.launch_logic import main + +# Get current file to locate start point of sys.argv +CURRENT_FILE = os.path.abspath(__file__) + + +def show_error_messagebox(title, message, detail_message=None): + """Function will show message and process ends after closing it.""" + from qtpy import QtWidgets, QtCore + from ayon_core import style + + app = QtWidgets.QApplication([]) + app.setStyleSheet(style.load_stylesheet()) + + msgbox = QtWidgets.QMessageBox() + msgbox.setWindowTitle(title) + msgbox.setText(message) + + if detail_message: + msgbox.setDetailedText(detail_message) + + msgbox.setWindowModality(QtCore.Qt.ApplicationModal) + msgbox.show() + + sys.exit(app.exec_()) + + +def on_invalid_args(script_not_found): + """Show to user message box saying that something went wrong. + + Tell user that arguments to launch implementation are invalid with + arguments details. + + Args: + script_not_found (bool): Use different message based on this value. + """ + + title = "Invalid arguments" + joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv) + if script_not_found: + submsg = "Where couldn't find script path:\n\"{}\"" + else: + submsg = "Expected Host executable after script path:\n\"{}\"" + + message = "BUG: Got invalid arguments so can't launch Host application." + detail_message = "Process was launched with arguments:\n{}\n\n{}".format( + joined_args, + submsg.format(CURRENT_FILE) + ) + + show_error_messagebox(title, message, detail_message) + + +def main(argv): + # Modify current file path to find match in sys.argv which may be different + # on windows (different letter cases and slashes). + modified_current_file = CURRENT_FILE.replace("\\", "/").lower() + + # Create a copy of sys argv + sys_args = list(argv) + after_script_idx = None + # Find script path in sys.argv to know index of argv where host + # executable should be. + for idx, item in enumerate(sys_args): + if item.replace("\\", "/").lower() == modified_current_file: + after_script_idx = idx + 1 + break + + # Validate that there is at least one argument after script path + launch_args = None + if after_script_idx is not None: + launch_args = sys_args[after_script_idx:] + + if launch_args: + # Launch host implementation + main(*launch_args) + else: + # Show message box + on_invalid_args(after_script_idx is None) + + +if __name__ == "__main__": + main(sys.argv) From 720de823cf973dab3d75bede890cf49dc7fb7efc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 15:54:36 +0100 Subject: [PATCH 16/37] aftereffects has own prelaunch hook to modify launch arguments --- .../hooks/pre_non_python_host_launch.py | 2 +- .../ayon_core/hosts/aftereffects/__init__.py | 8 +- client/ayon_core/hosts/aftereffects/addon.py | 17 ++++ .../hosts/aftereffects/api/launch_logic.py | 1 - .../aftereffects/hooks/pre_launch_args.py | 95 +++++++++++++++++++ 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py diff --git a/client/ayon_core/hooks/pre_non_python_host_launch.py b/client/ayon_core/hooks/pre_non_python_host_launch.py index 3d3c24b308..a65e0ecba4 100644 --- a/client/ayon_core/hooks/pre_non_python_host_launch.py +++ b/client/ayon_core/hooks/pre_non_python_host_launch.py @@ -17,7 +17,7 @@ class NonPythonHostHook(PreLaunchHook): python script which launch the host. For these cases it is necessary to prepend python (or ayon) executable and script path before application's. """ - app_groups = {"harmony", "aftereffects"} + app_groups = {"harmony"} order = 20 launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hosts/aftereffects/__init__.py b/client/ayon_core/hosts/aftereffects/__init__.py index ae750d05b6..02ab287629 100644 --- a/client/ayon_core/hosts/aftereffects/__init__.py +++ b/client/ayon_core/hosts/aftereffects/__init__.py @@ -1,6 +1,12 @@ -from .addon import AfterEffectsAddon +from .addon import ( + AFTEREFFECTS_ADDON_ROOT, + AfterEffectsAddon, + get_launch_script_path, +) __all__ = ( + "AFTEREFFECTS_ADDON_ROOT", "AfterEffectsAddon", + "get_launch_script_path", ) diff --git a/client/ayon_core/hosts/aftereffects/addon.py b/client/ayon_core/hosts/aftereffects/addon.py index 46d0818247..fc54043c1d 100644 --- a/client/ayon_core/hosts/aftereffects/addon.py +++ b/client/ayon_core/hosts/aftereffects/addon.py @@ -1,5 +1,9 @@ +import os + from ayon_core.addon import AYONAddon, IHostAddon +AFTEREFFECTS_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__)) + class AfterEffectsAddon(AYONAddon, IHostAddon): name = "aftereffects" @@ -17,3 +21,16 @@ class AfterEffectsAddon(AYONAddon, IHostAddon): def get_workfile_extensions(self): return [".aep"] + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(AFTEREFFECTS_ADDON_ROOT, "hooks") + ] + + +def get_launch_script_path(): + return os.path.join( + AFTEREFFECTS_ADDON_ROOT, "api", "launch_script.py" + ) diff --git a/client/ayon_core/hosts/aftereffects/api/launch_logic.py b/client/ayon_core/hosts/aftereffects/api/launch_logic.py index d0e4e8beae..5a23f2cb35 100644 --- a/client/ayon_core/hosts/aftereffects/api/launch_logic.py +++ b/client/ayon_core/hosts/aftereffects/api/launch_logic.py @@ -7,7 +7,6 @@ import asyncio import functools import traceback - from wsrpc_aiohttp import ( WebSocketRoute, WebSocketAsync diff --git a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py new file mode 100644 index 0000000000..2c5baa3b68 --- /dev/null +++ b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py @@ -0,0 +1,95 @@ +import os +import platform +import subprocess + +from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib.applications import ( + PreLaunchHook, + LaunchTypes, +) +from ayon_core.hosts.aftereffects import get_launch_script_path + + +def get_launch_kwargs(kwargs): + """Explicit setting of kwargs for Popen for AfterEffects. + + Expected behavior + - ayon_console opens window with logs + - ayon has stdout/stderr available for capturing + + Args: + kwargs (Union[dict, None]): Current kwargs or None. + + """ + if kwargs is None: + kwargs = {} + + if platform.system().lower() != "windows": + return kwargs + + executable_path = os.environ.get("AYON_EXECUTABLE") + + executable_filename = "" + if executable_path: + executable_filename = os.path.basename(executable_path) + + is_in_ui_launcher = "ayon_console" not in executable_filename + if is_in_ui_launcher: + kwargs.update({ + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL + }) + else: + kwargs.update({ + "creationflags": subprocess.CREATE_NEW_CONSOLE + }) + return kwargs + + +class AEPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and script path to AE implementation before + AE executable and add last workfile path to launch arguments. + + Existence of last workfile is checked. If workfile does not exists tries + to copy templated workfile from predefined path. + """ + app_groups = {"aftereffects"} + + order = 20 + launch_types = {LaunchTypes.local} + + def execute(self): + # Pop executable + executable_path = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + script_path = get_launch_script_path() + + new_launch_args = get_ayon_launcher_args( + "run", script_path, executable_path + ) + # Add workfile path if exists + workfile_path = self.data["last_workfile_path"] + if ( + self.data.get("start_last_workfile") + and workfile_path + and os.path.exists(workfile_path) + ): + new_launch_args.append(workfile_path) + + # Append as whole list as these arguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.launch_context.launch_args.extend(remainders) + + self.launch_context.kwargs = get_launch_kwargs( + self.launch_context.kwargs + ) From 96aac985c2545b5e8e5bf74a96e448a3b667463c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:23:23 +0100 Subject: [PATCH 17/37] copied non python launch script to harmony --- .../hosts/harmony/api/launch_script.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 client/ayon_core/hosts/harmony/api/launch_script.py diff --git a/client/ayon_core/hosts/harmony/api/launch_script.py b/client/ayon_core/hosts/harmony/api/launch_script.py new file mode 100644 index 0000000000..1f2c36b7e6 --- /dev/null +++ b/client/ayon_core/hosts/harmony/api/launch_script.py @@ -0,0 +1,93 @@ +"""Script wraps launch mechanism of Harmony implementations. + +Arguments passed to the script are passed to launch function in host +implementation. In all cases requires host app executable and may contain +workfile or others. +""" + +import os +import sys + +from ayon_core.hosts.harmony.api.lib import main + +# Get current file to locate start point of sys.argv +CURRENT_FILE = os.path.abspath(__file__) + + +def show_error_messagebox(title, message, detail_message=None): + """Function will show message and process ends after closing it.""" + from qtpy import QtWidgets, QtCore + from ayon_core import style + + app = QtWidgets.QApplication([]) + app.setStyleSheet(style.load_stylesheet()) + + msgbox = QtWidgets.QMessageBox() + msgbox.setWindowTitle(title) + msgbox.setText(message) + + if detail_message: + msgbox.setDetailedText(detail_message) + + msgbox.setWindowModality(QtCore.Qt.ApplicationModal) + msgbox.show() + + sys.exit(app.exec_()) + + +def on_invalid_args(script_not_found): + """Show to user message box saying that something went wrong. + + Tell user that arguments to launch implementation are invalid with + arguments details. + + Args: + script_not_found (bool): Use different message based on this value. + """ + + title = "Invalid arguments" + joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv) + if script_not_found: + submsg = "Where couldn't find script path:\n\"{}\"" + else: + submsg = "Expected Host executable after script path:\n\"{}\"" + + message = "BUG: Got invalid arguments so can't launch Host application." + detail_message = "Process was launched with arguments:\n{}\n\n{}".format( + joined_args, + submsg.format(CURRENT_FILE) + ) + + show_error_messagebox(title, message, detail_message) + + +def main(argv): + # Modify current file path to find match in sys.argv which may be different + # on windows (different letter cases and slashes). + modified_current_file = CURRENT_FILE.replace("\\", "/").lower() + + # Create a copy of sys argv + sys_args = list(argv) + after_script_idx = None + # Find script path in sys.argv to know index of argv where host + # executable should be. + for idx, item in enumerate(sys_args): + if item.replace("\\", "/").lower() == modified_current_file: + after_script_idx = idx + 1 + break + + # Validate that there is at least one argument after script path + launch_args = None + if after_script_idx is not None: + launch_args = sys_args[after_script_idx:] + + if launch_args: + # Launch host implementation + main(*launch_args) + else: + # Show message box + on_invalid_args(after_script_idx is None) + + +if __name__ == "__main__": + main(sys.argv) From da395ae5305cb63cd19140cc2b48296facc94809 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:23:57 +0100 Subject: [PATCH 18/37] renamed 'HARMONY_HOST_DIR' to 'HARMONY_ADDON_ROOT' --- client/ayon_core/hosts/harmony/__init__.py | 4 ++-- client/ayon_core/hosts/harmony/addon.py | 4 ++-- client/ayon_core/hosts/harmony/api/pipeline.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/harmony/__init__.py b/client/ayon_core/hosts/harmony/__init__.py index 9177eaa285..449bb96a4f 100644 --- a/client/ayon_core/hosts/harmony/__init__.py +++ b/client/ayon_core/hosts/harmony/__init__.py @@ -1,10 +1,10 @@ from .addon import ( - HARMONY_HOST_DIR, + HARMONY_ADDON_ROOT, HarmonyAddon, ) __all__ = ( - "HARMONY_HOST_DIR", + "HARMONY_ADDON_ROOT", "HarmonyAddon", ) diff --git a/client/ayon_core/hosts/harmony/addon.py b/client/ayon_core/hosts/harmony/addon.py index 476d569415..7f8776cd74 100644 --- a/client/ayon_core/hosts/harmony/addon.py +++ b/client/ayon_core/hosts/harmony/addon.py @@ -1,7 +1,7 @@ import os from ayon_core.addon import AYONAddon, IHostAddon -HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) +HARMONY_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__)) class HarmonyAddon(AYONAddon, IHostAddon): @@ -11,7 +11,7 @@ class HarmonyAddon(AYONAddon, IHostAddon): def add_implementation_envs(self, env, _app): """Modify environments to contain all required for implementation.""" openharmony_path = os.path.join( - HARMONY_HOST_DIR, "vendor", "OpenHarmony" + HARMONY_ADDON_ROOT, "vendor", "OpenHarmony" ) # TODO check if is already set? What to do if is already set? env["LIB_OPENHARMONY_PATH"] = openharmony_path diff --git a/client/ayon_core/hosts/harmony/api/pipeline.py b/client/ayon_core/hosts/harmony/api/pipeline.py index a753a32ebb..d842ccd414 100644 --- a/client/ayon_core/hosts/harmony/api/pipeline.py +++ b/client/ayon_core/hosts/harmony/api/pipeline.py @@ -15,13 +15,13 @@ from ayon_core.pipeline import ( from ayon_core.pipeline.load import get_outdated_containers from ayon_core.pipeline.context_tools import get_current_project_folder -from ayon_core.hosts.harmony import HARMONY_HOST_DIR +from ayon_core.hosts.harmony import HARMONY_ADDON_ROOT import ayon_core.hosts.harmony.api as harmony log = logging.getLogger("ayon_core.hosts.harmony") -PLUGINS_DIR = os.path.join(HARMONY_HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(HARMONY_ADDON_ROOT, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") From 2119e735828c44ea23fc4a9d463382e474f7367a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:25:14 +0100 Subject: [PATCH 19/37] harmony has own prelaunch hook --- .../hooks/pre_non_python_host_launch.py | 58 ----------- client/ayon_core/hosts/harmony/__init__.py | 2 + client/ayon_core/hosts/harmony/addon.py | 13 +++ .../hosts/harmony/hooks/pre_launch_args.py | 95 +++++++++++++++++++ 4 files changed, 110 insertions(+), 58 deletions(-) delete mode 100644 client/ayon_core/hooks/pre_non_python_host_launch.py create mode 100644 client/ayon_core/hosts/harmony/hooks/pre_launch_args.py diff --git a/client/ayon_core/hooks/pre_non_python_host_launch.py b/client/ayon_core/hooks/pre_non_python_host_launch.py deleted file mode 100644 index a65e0ecba4..0000000000 --- a/client/ayon_core/hooks/pre_non_python_host_launch.py +++ /dev/null @@ -1,58 +0,0 @@ -import os - -from ayon_core.lib import get_ayon_launcher_args -from ayon_core.lib.applications import ( - get_non_python_host_kwargs, - PreLaunchHook, - LaunchTypes, -) - -from ayon_core import AYON_CORE_ROOT - - -class NonPythonHostHook(PreLaunchHook): - """Launch arguments preparation. - - Non python host implementation do not launch host directly but use - python script which launch the host. For these cases it is necessary to - prepend python (or ayon) executable and script path before application's. - """ - app_groups = {"harmony"} - - order = 20 - launch_types = {LaunchTypes.local} - - def execute(self): - # Pop executable - executable_path = self.launch_context.launch_args.pop(0) - - # Pop rest of launch arguments - There should not be other arguments! - remainders = [] - while self.launch_context.launch_args: - remainders.append(self.launch_context.launch_args.pop(0)) - - script_path = os.path.join( - AYON_CORE_ROOT, - "scripts", - "non_python_host_launch.py" - ) - - new_launch_args = get_ayon_launcher_args( - "run", script_path, executable_path - ) - # Add workfile path if exists - workfile_path = self.data["last_workfile_path"] - if ( - self.data.get("start_last_workfile") - and workfile_path - and os.path.exists(workfile_path)): - new_launch_args.append(workfile_path) - - # Append as whole list as these areguments should not be separated - self.launch_context.launch_args.append(new_launch_args) - - if remainders: - self.launch_context.launch_args.extend(remainders) - - self.launch_context.kwargs = \ - get_non_python_host_kwargs(self.launch_context.kwargs) diff --git a/client/ayon_core/hosts/harmony/__init__.py b/client/ayon_core/hosts/harmony/__init__.py index 449bb96a4f..6454d6f9d7 100644 --- a/client/ayon_core/hosts/harmony/__init__.py +++ b/client/ayon_core/hosts/harmony/__init__.py @@ -1,10 +1,12 @@ from .addon import ( HARMONY_ADDON_ROOT, HarmonyAddon, + get_launch_script_path, ) __all__ = ( "HARMONY_ADDON_ROOT", "HarmonyAddon", + "get_launch_script_path", ) diff --git a/client/ayon_core/hosts/harmony/addon.py b/client/ayon_core/hosts/harmony/addon.py index 7f8776cd74..1915a7eb6f 100644 --- a/client/ayon_core/hosts/harmony/addon.py +++ b/client/ayon_core/hosts/harmony/addon.py @@ -18,3 +18,16 @@ class HarmonyAddon(AYONAddon, IHostAddon): def get_workfile_extensions(self): return [".zip"] + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(HARMONY_ADDON_ROOT, "hooks") + ] + + +def get_launch_script_path(): + return os.path.join( + HARMONY_ADDON_ROOT, "api", "launch_script.py" + ) diff --git a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py new file mode 100644 index 0000000000..1a36c890a7 --- /dev/null +++ b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py @@ -0,0 +1,95 @@ +import os +import platform +import subprocess + +from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib.applications import ( + PreLaunchHook, + LaunchTypes, +) +from ayon_core.hosts.harmony import get_launch_script_path + + +def get_launch_kwargs(kwargs): + """Explicit setting of kwargs for Popen for Harmony. + + Expected behavior + - ayon_console opens window with logs + - ayon has stdout/stderr available for capturing + + Args: + kwargs (Union[dict, None]): Current kwargs or None. + + """ + if kwargs is None: + kwargs = {} + + if platform.system().lower() != "windows": + return kwargs + + executable_path = os.environ.get("AYON_EXECUTABLE") + + executable_filename = "" + if executable_path: + executable_filename = os.path.basename(executable_path) + + is_gui_executable = "ayon_console" not in executable_filename + if is_gui_executable: + kwargs.update({ + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL + }) + else: + kwargs.update({ + "creationflags": subprocess.CREATE_NEW_CONSOLE + }) + return kwargs + + +class HarmonyPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and script path to Harmony implementation + before Harmony executable and add last workfile path to launch arguments. + + Existence of last workfile is checked. If workfile does not exists tries + to copy templated workfile from predefined path. + """ + app_groups = {"harmony"} + + order = 20 + launch_types = {LaunchTypes.local} + + def execute(self): + # Pop executable + executable_path = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + script_path = get_launch_script_path() + + new_launch_args = get_ayon_launcher_args( + "run", script_path, executable_path + ) + # Add workfile path if exists + workfile_path = self.data["last_workfile_path"] + if ( + self.data.get("start_last_workfile") + and workfile_path + and os.path.exists(workfile_path) + ): + new_launch_args.append(workfile_path) + + # Append as whole list as these arguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.launch_context.launch_args.extend(remainders) + + self.launch_context.kwargs = get_launch_kwargs( + self.launch_context.kwargs + ) From 4fb898615bff67a20925622e297859b369db6477 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:28:59 +0100 Subject: [PATCH 20/37] remove unused global script --- .../scripts/non_python_host_launch.py | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 client/ayon_core/scripts/non_python_host_launch.py diff --git a/client/ayon_core/scripts/non_python_host_launch.py b/client/ayon_core/scripts/non_python_host_launch.py deleted file mode 100644 index 4c18fd0ccc..0000000000 --- a/client/ayon_core/scripts/non_python_host_launch.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Script wraps launch mechanism of non python host implementations. - -Arguments passed to the script are passed to launch function in host -implementation. In all cases requires host app executable and may contain -workfile or others. -""" - -import os -import sys - -# Get current file to locate start point of sys.argv -CURRENT_FILE = os.path.abspath(__file__) - - -def show_error_messagebox(title, message, detail_message=None): - """Function will show message and process ends after closing it.""" - from qtpy import QtWidgets, QtCore - from ayon_core import style - - app = QtWidgets.QApplication([]) - app.setStyleSheet(style.load_stylesheet()) - - msgbox = QtWidgets.QMessageBox() - msgbox.setWindowTitle(title) - msgbox.setText(message) - - if detail_message: - msgbox.setDetailedText(detail_message) - - msgbox.setWindowModality(QtCore.Qt.ApplicationModal) - msgbox.show() - - sys.exit(app.exec_()) - - -def on_invalid_args(script_not_found): - """Show to user message box saying that something went wrong. - - Tell user that arguments to launch implementation are invalid with - arguments details. - - Args: - script_not_found (bool): Use different message based on this value. - """ - - title = "Invalid arguments" - joined_args = ", ".join("\"{}\"".format(arg) for arg in sys.argv) - if script_not_found: - submsg = "Where couldn't find script path:\n\"{}\"" - else: - submsg = "Expected Host executable after script path:\n\"{}\"" - - message = "BUG: Got invalid arguments so can't launch Host application." - detail_message = "Process was launched with arguments:\n{}\n\n{}".format( - joined_args, - submsg.format(CURRENT_FILE) - ) - - show_error_messagebox(title, message, detail_message) - - -def main(argv): - # Modify current file path to find match in sys.argv which may be different - # on windows (different letter cases and slashes). - modified_current_file = CURRENT_FILE.replace("\\", "/").lower() - - # Create a copy of sys argv - sys_args = list(argv) - after_script_idx = None - # Find script path in sys.argv to know index of argv where host - # executable should be. - for idx, item in enumerate(sys_args): - if item.replace("\\", "/").lower() == modified_current_file: - after_script_idx = idx + 1 - break - - # Validate that there is at least one argument after script path - launch_args = None - if after_script_idx is not None: - launch_args = sys_args[after_script_idx:] - - host_name = os.environ["AYON_HOST_NAME"].lower() - if host_name == "photoshop": - # TODO refactor launch logic according to AE - from ayon_core.hosts.photoshop.api.lib import main - elif host_name == "aftereffects": - from ayon_core.hosts.aftereffects.api.launch_logic import main - elif host_name == "harmony": - from ayon_core.hosts.harmony.api.lib import main - else: - title = "Unknown host name" - message = ( - "BUG: Environment variable AYON_HOST_NAME contains unknown" - " host name \"{}\"" - ).format(host_name) - show_error_messagebox(title, message) - return - - if launch_args: - # Launch host implementation - main(*launch_args) - else: - # Show message box - on_invalid_args(after_script_idx is None) - - -if __name__ == "__main__": - main(sys.argv) From 2c07fb45032d28162c32661cd977c36f20058aec Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:29:16 +0100 Subject: [PATCH 21/37] removed unused 'get_non_python_host_kwargs' --- client/ayon_core/lib/applications.py | 39 ---------------------------- 1 file changed, 39 deletions(-) diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index 4bf0c31d93..68fc3ea201 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -1985,42 +1985,3 @@ def should_workfile_tool_start( if output is None: return default_output return output - - -def get_non_python_host_kwargs(kwargs, allow_console=True): - """Explicit setting of kwargs for Popen for AE/PS/Harmony. - - Expected behavior - - ayon_console opens window with logs - - ayon has stdout/stderr available for capturing - - Args: - kwargs (dict) or None - allow_console (bool): use False for inner Popen opening app itself or - it will open additional console (at least for Harmony) - """ - - if kwargs is None: - kwargs = {} - - if platform.system().lower() != "windows": - return kwargs - - executable_path = os.environ.get("AYON_EXECUTABLE") - - executable_filename = "" - if executable_path: - executable_filename = os.path.basename(executable_path) - - is_gui_executable = "ayon_console" not in executable_filename - if is_gui_executable: - kwargs.update({ - "creationflags": subprocess.CREATE_NO_WINDOW, - "stdout": subprocess.DEVNULL, - "stderr": subprocess.DEVNULL - }) - elif allow_console: - kwargs.update({ - "creationflags": subprocess.CREATE_NEW_CONSOLE - }) - return kwargs From ca39fc429130942c85883d471dd5604efad235dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:31:11 +0100 Subject: [PATCH 22/37] don't use 'get_non_python_host_kwargs' when launching harmony --- client/ayon_core/hosts/harmony/api/lib.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index bc73e19066..34cb14636d 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Utility functions used for Avalon - Harmony integration.""" +import platform import subprocess import threading import os @@ -22,7 +23,6 @@ from .server import Server from ayon_core.tools.stdout_broker.app import StdOutBroker from ayon_core.tools.utils import host_tools from ayon_core import style -from ayon_core.lib.applications import get_non_python_host_kwargs # Setup logging. log = logging.getLogger(__name__) @@ -324,7 +324,18 @@ def launch_zip_file(filepath): return print("Launching {}".format(scene_path)) - kwargs = get_non_python_host_kwargs({}, False) + kwargs = {} + if platform.system().lower() == "windows": + executable_filename = os.path.basename( + os.getenv("AYON_EXECUTABLE", "") + ) + if "ayon_console" not in executable_filename: + kwargs.update({ + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL + }) + process = subprocess.Popen( [ProcessContext.application_path, scene_path], **kwargs From 508d4b559e7ed8db2da14c3e1959b403efb33b43 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:32:17 +0100 Subject: [PATCH 23/37] added comment with question --- client/ayon_core/hosts/harmony/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index 34cb14636d..b78fe468b5 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -324,6 +324,7 @@ def launch_zip_file(filepath): return print("Launching {}".format(scene_path)) + # QUESTION Could we use 'run_detached_process' from 'ayon_core.lib'? kwargs = {} if platform.system().lower() == "windows": executable_filename = os.path.basename( From 562730fa65384e89fe1c72cd981c0e997200641a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:33:02 +0100 Subject: [PATCH 24/37] added helper function to determine if ui executable is used --- .../aftereffects/hooks/pre_launch_args.py | 14 +++----- client/ayon_core/hosts/harmony/api/lib.py | 25 +++++++------- .../hosts/harmony/hooks/pre_launch_args.py | 14 +++----- .../hosts/photoshop/hooks/pre_launch_args.py | 14 +++----- client/ayon_core/lib/__init__.py | 2 ++ client/ayon_core/lib/ayon_info.py | 34 ++++++++++++++++++- 6 files changed, 63 insertions(+), 40 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py index 2c5baa3b68..76ccd2bd4f 100644 --- a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py @@ -2,7 +2,10 @@ import os import platform import subprocess -from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib import ( + get_ayon_launcher_args, + is_using_ui_executable, +) from ayon_core.lib.applications import ( PreLaunchHook, LaunchTypes, @@ -27,14 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - executable_path = os.environ.get("AYON_EXECUTABLE") - - executable_filename = "" - if executable_path: - executable_filename = os.path.basename(executable_path) - - is_in_ui_launcher = "ayon_console" not in executable_filename - if is_in_ui_launcher: + if is_using_ui_executable(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index b78fe468b5..828ee3863e 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -15,15 +15,17 @@ import json import signal import time from uuid import uuid4 -from qtpy import QtWidgets, QtCore, QtGui import collections -from .server import Server +from qtpy import QtWidgets, QtCore, QtGui +from ayon_core.lib import is_using_ui_executable from ayon_core.tools.stdout_broker.app import StdOutBroker from ayon_core.tools.utils import host_tools from ayon_core import style +from .server import Server + # Setup logging. log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -326,16 +328,15 @@ def launch_zip_file(filepath): print("Launching {}".format(scene_path)) # QUESTION Could we use 'run_detached_process' from 'ayon_core.lib'? kwargs = {} - if platform.system().lower() == "windows": - executable_filename = os.path.basename( - os.getenv("AYON_EXECUTABLE", "") - ) - if "ayon_console" not in executable_filename: - kwargs.update({ - "creationflags": subprocess.CREATE_NO_WINDOW, - "stdout": subprocess.DEVNULL, - "stderr": subprocess.DEVNULL - }) + if ( + platform.system().lower() == "windows" + and is_using_ui_executable() + ): + kwargs.update({ + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL + }) process = subprocess.Popen( [ProcessContext.application_path, scene_path], diff --git a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py index 1a36c890a7..c2c667c1d8 100644 --- a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py @@ -2,7 +2,10 @@ import os import platform import subprocess -from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib import ( + get_ayon_launcher_args, + is_using_ui_executable, +) from ayon_core.lib.applications import ( PreLaunchHook, LaunchTypes, @@ -27,14 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - executable_path = os.environ.get("AYON_EXECUTABLE") - - executable_filename = "" - if executable_path: - executable_filename = os.path.basename(executable_path) - - is_gui_executable = "ayon_console" not in executable_filename - if is_gui_executable: + if is_using_ui_executable(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py index 0689a6c2f0..228413e1ea 100644 --- a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py @@ -2,7 +2,10 @@ import os import platform import subprocess -from ayon_core.lib import get_ayon_launcher_args +from ayon_core.lib import ( + get_ayon_launcher_args, + is_using_ui_executable, +) from ayon_core.lib.applications import ( PreLaunchHook, LaunchTypes, @@ -27,14 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - executable_path = os.environ.get("AYON_EXECUTABLE") - - executable_filename = "" - if executable_path: - executable_filename = os.path.basename(executable_path) - - is_gui_executable = "ayon_console" not in executable_filename - if is_gui_executable: + if is_using_ui_executable(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index f69fd10b07..d82fb0de0e 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -155,6 +155,7 @@ from .path_tools import ( from .ayon_info import ( is_running_from_build, + is_using_ui_executable, is_staging_enabled, is_dev_mode_enabled, is_in_tests, @@ -275,6 +276,7 @@ __all__ = [ "Logger", "is_running_from_build", + "is_using_ui_executable", "is_staging_enabled", "is_dev_mode_enabled", "is_in_tests", diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index ec37d735d8..3d4c38c99a 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -10,6 +10,12 @@ from .local_settings import get_local_site_id def get_ayon_launcher_version(): + """Get AYON launcher version. + + Returns: + str: Version string. + + """ version_filepath = os.path.join(os.environ["AYON_ROOT"], "version.py") if not os.path.exists(version_filepath): return None @@ -24,8 +30,8 @@ def is_running_from_build(): Returns: bool: True if running from build. - """ + """ executable_path = os.environ["AYON_EXECUTABLE"] executable_filename = os.path.basename(executable_path) if "python" in executable_filename.lower(): @@ -33,6 +39,32 @@ def is_running_from_build(): return True +def is_using_ui_executable(): + """AYON launcher UI windows executable is used. + + This function make sense only on Windows platform. For other platforms + always returns False. False is also returned if process is running from + code. + + AYON launcher on windows has 2 executable files. First 'ayon_console.exe' + works as 'python.exe' executable, the second 'ayon.exe' works as + 'pythonw.exe' executable. The difference is way how stdout/stderr is + handled (especially when calling subprocess). + + Returns: + bool: True if UI executable is used. + + """ + if ( + platform.system().lower() != "windows" + or is_running_from_build() + ): + return False + executable_path = os.environ["AYON_EXECUTABLE"] + executable_filename = os.path.basename(executable_path) + return "ayon_console" not in executable_filename + + def is_staging_enabled(): return os.getenv("AYON_USE_STAGING") == "1" From 5cb07274d1fea5818c0e431c99aa0b3c36a7918c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:45:27 +0100 Subject: [PATCH 25/37] renamed 'is_using_ui_executable' to 'is_using_ayon_console' --- client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py | 4 ++-- client/ayon_core/hosts/harmony/api/lib.py | 4 ++-- client/ayon_core/hosts/harmony/hooks/pre_launch_args.py | 4 ++-- client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py | 4 ++-- client/ayon_core/lib/__init__.py | 4 ++-- client/ayon_core/lib/ayon_info.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py index 76ccd2bd4f..959df0eb39 100644 --- a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py @@ -4,7 +4,7 @@ import subprocess from ayon_core.lib import ( get_ayon_launcher_args, - is_using_ui_executable, + is_using_ayon_console, ) from ayon_core.lib.applications import ( PreLaunchHook, @@ -30,7 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - if is_using_ui_executable(): + if is_using_ayon_console(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index 828ee3863e..d9c17c6214 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -19,7 +19,7 @@ import collections from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.lib import is_using_ui_executable +from ayon_core.lib import is_using_ayon_console from ayon_core.tools.stdout_broker.app import StdOutBroker from ayon_core.tools.utils import host_tools from ayon_core import style @@ -330,7 +330,7 @@ def launch_zip_file(filepath): kwargs = {} if ( platform.system().lower() == "windows" - and is_using_ui_executable() + and is_using_ayon_console() ): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, diff --git a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py index c2c667c1d8..66bc0a80d8 100644 --- a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py @@ -4,7 +4,7 @@ import subprocess from ayon_core.lib import ( get_ayon_launcher_args, - is_using_ui_executable, + is_using_ayon_console, ) from ayon_core.lib.applications import ( PreLaunchHook, @@ -30,7 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - if is_using_ui_executable(): + if is_using_ayon_console(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py index 228413e1ea..7fe6e7437f 100644 --- a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py @@ -4,7 +4,7 @@ import subprocess from ayon_core.lib import ( get_ayon_launcher_args, - is_using_ui_executable, + is_using_ayon_console, ) from ayon_core.lib.applications import ( PreLaunchHook, @@ -30,7 +30,7 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - if is_using_ui_executable(): + if is_using_ayon_console(): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index d82fb0de0e..10e290360d 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -155,7 +155,7 @@ from .path_tools import ( from .ayon_info import ( is_running_from_build, - is_using_ui_executable, + is_using_ayon_console, is_staging_enabled, is_dev_mode_enabled, is_in_tests, @@ -276,7 +276,7 @@ __all__ = [ "Logger", "is_running_from_build", - "is_using_ui_executable", + "is_using_ayon_console", "is_staging_enabled", "is_dev_mode_enabled", "is_in_tests", diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index 3d4c38c99a..c3bda70834 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -39,7 +39,7 @@ def is_running_from_build(): return True -def is_using_ui_executable(): +def is_using_ayon_console(): """AYON launcher UI windows executable is used. This function make sense only on Windows platform. For other platforms From e15460d851dd56869cbcdbec52dde1d72aef25bb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 25 Mar 2024 16:51:03 +0100 Subject: [PATCH 26/37] fix the reversed logic --- .../hosts/aftereffects/hooks/pre_launch_args.py | 8 ++++---- client/ayon_core/hosts/harmony/api/lib.py | 2 +- .../ayon_core/hosts/harmony/hooks/pre_launch_args.py | 8 ++++---- .../ayon_core/hosts/photoshop/hooks/pre_launch_args.py | 10 +++++----- client/ayon_core/lib/ayon_info.py | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py index 959df0eb39..979d9ff3e5 100644 --- a/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/aftereffects/hooks/pre_launch_args.py @@ -32,13 +32,13 @@ def get_launch_kwargs(kwargs): if is_using_ayon_console(): kwargs.update({ - "creationflags": subprocess.CREATE_NO_WINDOW, - "stdout": subprocess.DEVNULL, - "stderr": subprocess.DEVNULL + "creationflags": subprocess.CREATE_NEW_CONSOLE }) else: kwargs.update({ - "creationflags": subprocess.CREATE_NEW_CONSOLE + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL }) return kwargs diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index d9c17c6214..3c833c7b69 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -330,7 +330,7 @@ def launch_zip_file(filepath): kwargs = {} if ( platform.system().lower() == "windows" - and is_using_ayon_console() + and not is_using_ayon_console() ): kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, diff --git a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py index 66bc0a80d8..bbad14084a 100644 --- a/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/harmony/hooks/pre_launch_args.py @@ -32,13 +32,13 @@ def get_launch_kwargs(kwargs): if is_using_ayon_console(): kwargs.update({ - "creationflags": subprocess.CREATE_NO_WINDOW, - "stdout": subprocess.DEVNULL, - "stderr": subprocess.DEVNULL + "creationflags": subprocess.CREATE_NEW_CONSOLE }) else: kwargs.update({ - "creationflags": subprocess.CREATE_NEW_CONSOLE + "creationflags": subprocess.CREATE_NO_WINDOW, + "stdout": subprocess.DEVNULL, + "stderr": subprocess.DEVNULL }) return kwargs diff --git a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py index 7fe6e7437f..8358c11ca1 100644 --- a/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py +++ b/client/ayon_core/hosts/photoshop/hooks/pre_launch_args.py @@ -30,16 +30,16 @@ def get_launch_kwargs(kwargs): if platform.system().lower() != "windows": return kwargs - if is_using_ayon_console(): + if not is_using_ayon_console(): + kwargs.update({ + "creationflags": subprocess.CREATE_NEW_CONSOLE + }) + else: kwargs.update({ "creationflags": subprocess.CREATE_NO_WINDOW, "stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL }) - else: - kwargs.update({ - "creationflags": subprocess.CREATE_NEW_CONSOLE - }) return kwargs diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index c3bda70834..adb3c1befc 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -40,10 +40,10 @@ def is_running_from_build(): def is_using_ayon_console(): - """AYON launcher UI windows executable is used. + """AYON launcher console executable is used. This function make sense only on Windows platform. For other platforms - always returns False. False is also returned if process is running from + always returns True. True is also returned if process is running from code. AYON launcher on windows has 2 executable files. First 'ayon_console.exe' @@ -52,17 +52,17 @@ def is_using_ayon_console(): handled (especially when calling subprocess). Returns: - bool: True if UI executable is used. + bool: True if console executable is used. """ if ( platform.system().lower() != "windows" or is_running_from_build() ): - return False + return True executable_path = os.environ["AYON_EXECUTABLE"] executable_filename = os.path.basename(executable_path) - return "ayon_console" not in executable_filename + return "ayon_console" in executable_filename def is_staging_enabled(): From 769f80e923a07435c0a6813a4c20048ba4d8f3c4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 20:43:55 +0100 Subject: [PATCH 27/37] Import validation message --- .../help/validate_rig_out_set_node_ids.xml | 32 +++++++++++++++++++ .../publish/validate_rig_out_set_node_ids.py | 18 +++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 client/ayon_core/hosts/maya/plugins/publish/help/validate_rig_out_set_node_ids.xml diff --git a/client/ayon_core/hosts/maya/plugins/publish/help/validate_rig_out_set_node_ids.xml b/client/ayon_core/hosts/maya/plugins/publish/help/validate_rig_out_set_node_ids.xml new file mode 100644 index 0000000000..374b8e59ae --- /dev/null +++ b/client/ayon_core/hosts/maya/plugins/publish/help/validate_rig_out_set_node_ids.xml @@ -0,0 +1,32 @@ + + + +Shape IDs mismatch original shape +## Shapes mismatch IDs with original shape + +Meshes are detected in the **rig** where the (deformed) mesh has a different +`cbId` than the same mesh in its deformation history. +Theses should normally be the same. + +### How to repair? + +By using the repair action the IDs from the shape in history will be +copied to the deformed shape. For rig instances, in many cases the +correct fix is to use the repair action **unless** you explicitly tried +to update the `cbId` values on the meshes - in that case you actually want +to do to the reverse and copy the IDs from the deformed mesh to the history +mesh instead. + + + +### How does this happen? + +When a deformer is applied in the scene on a referenced mesh that had no +deformers then Maya will create a new shape node for the mesh that +does not have the original id. Then on scene save new ids get created for the +meshes lacking a `cbId` and thus the mesh then has a different `cbId` than +the mesh in the deformation history. + + + + diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index ab8cc25210..c9e779e999 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -7,7 +7,7 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, - PublishValidationError, + PublishXmlValidationError, OptionalPyblishPluginMixin ) @@ -42,8 +42,20 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin, # if a deformer has been created on the shape invalid = self.get_invalid(instance) if invalid: - raise PublishValidationError( - "Nodes found with mismatching IDs: {0}".format(invalid) + + # Use the short names + invalid = cmds.ls(invalid) + invalid.sort() + + # Construct a human-readable list + invalid = "\n".join("- {}".format(node) for node in invalid) + + raise PublishXmlValidationError( + plugin=ValidateRigOutSetNodeIds, + message=( + "Rig nodes have different IDs than their input " + "history: \n{0}".format(invalid) + ) ) @classmethod From 3cee75065734549542277bf0ee9585404343fe59 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 22:10:08 +0100 Subject: [PATCH 28/37] Fix instance node --- .../ayon_core/hosts/maya/plugins/publish/validate_step_size.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py index a3419a83a9..cad584d8f9 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py @@ -29,7 +29,7 @@ class ValidateStepSize(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): - objset = instance.data['name'] + objset = instance.data['instance_node'] step = instance.data.get("step", 1.0) if step < cls.MIN or step > cls.MAX: From f95d172c55f1fba372d8b7b501894016b34fde23 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 22:10:26 +0100 Subject: [PATCH 29/37] Improve validation message --- .../ayon_core/hosts/maya/plugins/publish/validate_step_size.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py index cad584d8f9..a276a5b644 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_step_size.py @@ -47,4 +47,4 @@ class ValidateStepSize(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - "Invalid instances found: {0}".format(invalid)) + "Instance found with invalid step size: {0}".format(invalid)) From d0da71c45cc2ec80a8b6fa8cc66db36393c81bbb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 10:14:39 +0100 Subject: [PATCH 30/37] Improve readability of the artist facing report --- .../maya/plugins/publish/validate_no_namespace.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py b/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py index 7ea2a79339..f546caff2c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_no_namespace.py @@ -49,11 +49,17 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + invalid_namespaces = {get_namespace(node) for node in invalid} raise PublishValidationError( - "Namespaces found:\n\n{0}".format( - _as_report_list(sorted(invalid)) + message="Namespaces found:\n\n{0}".format( + _as_report_list(sorted(invalid_namespaces)) ), - title="Namespaces in model" + title="Namespaces in model", + description=( + "## Namespaces found in model\n" + "It is not allowed to publish a model that contains " + "namespaces." + ) ) @classmethod From fe8613b55e14d529ad76d9cd8a1e25e13631f6af Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 10:38:20 +0100 Subject: [PATCH 31/37] Improve description, remove docstring which was only about the `bake_attributes` to begin with --- server_addon/maya/server/settings/publishers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index fe5c10e93c..7e1cdbf167 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -315,16 +315,13 @@ class ExtractMayaSceneRawModel(BaseSettingsModel): class ExtractCameraAlembicModel(BaseSettingsModel): - """ - List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax. - """ enabled: bool = SettingsField(title="ExtractCameraAlembic") optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") bake_attributes: str = SettingsField( "[]", title="Bake Attributes", widget="textarea", description="List of attributes that will be included in the alembic " - "export.", + "camera export. Needs to be written as a JSON list.", ) @validator("bake_attributes") From 08a6cbc57c33bf346bbc4364d9473bc9d312a4be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 11:57:15 +0100 Subject: [PATCH 32/37] Ensure unique class name compared to `extract_yeti_cache.py` --- .../hosts/maya/plugins/publish/extract_unreal_yeticache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_yeticache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_yeticache.py index 9a264959d1..9a6b4ebaed 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_yeticache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_yeticache.py @@ -5,13 +5,13 @@ from maya import cmds from ayon_core.pipeline import publish -class ExtractYetiCache(publish.Extractor): +class ExtractUnrealYetiCache(publish.Extractor): """Producing Yeti cache files using scene time range. This will extract Yeti cache file sequence and fur settings. """ - label = "Extract Yeti Cache" + label = "Extract Yeti Cache (Unreal)" hosts = ["maya"] families = ["yeticacheUE"] From 3526a1e52e60aef2892ad26892eac8c677ece6a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Mar 2024 16:02:44 +0100 Subject: [PATCH 33/37] use correct source for product type --- .../hosts/maya/plugins/load/load_redshift_proxy.py | 5 +---- client/ayon_core/hosts/maya/plugins/load/load_reference.py | 6 +----- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py | 5 +---- .../hosts/maya/plugins/load/load_vdb_to_redshift.py | 5 +---- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py | 5 +---- client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py | 5 +---- client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py | 5 +---- client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py | 5 +---- 8 files changed, 8 insertions(+), 33 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py index 63dae87243..0f91d9048a 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py @@ -32,10 +32,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, options=None): """Plugin entry point.""" - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "redshiftproxy" + product_type = context["product"]["productType"] folder_name = context["folder"]["name"] namespace = namespace or unique_namespace( diff --git a/client/ayon_core/hosts/maya/plugins/load/load_reference.py b/client/ayon_core/hosts/maya/plugins/load/load_reference.py index fdd85eda43..de18b2b0ec 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -117,11 +117,7 @@ class ReferenceLoader(plugin.ReferenceLoader): def process_reference(self, context, name, namespace, options): import maya.cmds as cmds - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "model" - + product_type = context["product"]["productType"] project_name = context["project"]["name"] # True by default to keep legacy behaviours attach_to_root = options.get("attach_to_root", True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py index f0fb89e5a4..3d984fdc79 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -25,10 +25,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): from ayon_core.hosts.maya.api.pipeline import containerise from ayon_core.hosts.maya.api.lib import unique_namespace - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "vdbcache" + product_type = context["product"]["productType"] # Check if the plugin for arnold is available on the pc try: diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py index cad0900590..3fa490f405 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -31,10 +31,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): from ayon_core.hosts.maya.api.pipeline import containerise from ayon_core.hosts.maya.api.lib import unique_namespace - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "vdbcache" + product_type = context["product"]["productType"] # Check if the plugin for redshift is available on the pc try: diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py index 88f62e81a4..7b87c21f38 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -94,10 +94,7 @@ class LoadVDBtoVRay(load.LoaderPlugin): "Path does not exist: %s" % path ) - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "vdbcache" + product_type = context["product"]["productType"] # Ensure V-ray is loaded with the vrayvolumegrid if not cmds.pluginInfo("vrayformaya", query=True, loaded=True): diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py index 59d8eadefa..895a4a4127 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -47,10 +47,7 @@ class VRayProxyLoader(load.LoaderPlugin): """ - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "vrayproxy" + product_type = context["product"]["productType"] # get all representations for this version filename = self._get_abc( diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py index 2f4ab1d080..36a25e2af1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -26,10 +26,7 @@ class VRaySceneLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, data): - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "vrayscene_layer" + product_type = context["product"]["productType"] folder_name = context["folder"]["name"] namespace = namespace or unique_namespace( diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py index 8933c4d8a6..a5cd04b0f4 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py @@ -56,10 +56,7 @@ class YetiCacheLoader(load.LoaderPlugin): """ - try: - product_type = context["representation"]["context"]["family"] - except ValueError: - product_type = "yeticache" + product_type = context["product"]["productType"] # Build namespace folder_name = context["folder"]["name"] From cd0a3005d3d2cfc9418c4d32fa1fbac296b4d93b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 17:27:31 +0100 Subject: [PATCH 34/37] Fix - differentiate between host main and main --- client/ayon_core/hosts/aftereffects/api/launch_script.py | 4 ++-- client/ayon_core/hosts/harmony/api/launch_script.py | 4 ++-- client/ayon_core/hosts/photoshop/api/launch_script.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/api/launch_script.py b/client/ayon_core/hosts/aftereffects/api/launch_script.py index ad4e779bd0..87926c022b 100644 --- a/client/ayon_core/hosts/aftereffects/api/launch_script.py +++ b/client/ayon_core/hosts/aftereffects/api/launch_script.py @@ -8,7 +8,7 @@ workfile or others. import os import sys -from ayon_core.hosts.aftereffects.api.launch_logic import main +from ayon_core.hosts.aftereffects.api.launch_logic import main as host_main # Get current file to locate start point of sys.argv CURRENT_FILE = os.path.abspath(__file__) @@ -83,7 +83,7 @@ def main(argv): if launch_args: # Launch host implementation - main(*launch_args) + host_main(*launch_args) else: # Show message box on_invalid_args(after_script_idx is None) diff --git a/client/ayon_core/hosts/harmony/api/launch_script.py b/client/ayon_core/hosts/harmony/api/launch_script.py index 1f2c36b7e6..3c809e210f 100644 --- a/client/ayon_core/hosts/harmony/api/launch_script.py +++ b/client/ayon_core/hosts/harmony/api/launch_script.py @@ -8,7 +8,7 @@ workfile or others. import os import sys -from ayon_core.hosts.harmony.api.lib import main +from ayon_core.hosts.harmony.api.lib import main as host_main # Get current file to locate start point of sys.argv CURRENT_FILE = os.path.abspath(__file__) @@ -83,7 +83,7 @@ def main(argv): if launch_args: # Launch host implementation - main(*launch_args) + host_main(*launch_args) else: # Show message box on_invalid_args(after_script_idx is None) diff --git a/client/ayon_core/hosts/photoshop/api/launch_script.py b/client/ayon_core/hosts/photoshop/api/launch_script.py index c036b63c46..bb4de80086 100644 --- a/client/ayon_core/hosts/photoshop/api/launch_script.py +++ b/client/ayon_core/hosts/photoshop/api/launch_script.py @@ -8,7 +8,7 @@ workfile or others. import os import sys -from ayon_core.hosts.photoshop.api.lib import main +from ayon_core.hosts.photoshop.api.lib import main as host_main # Get current file to locate start point of sys.argv CURRENT_FILE = os.path.abspath(__file__) @@ -83,7 +83,7 @@ def main(argv): if launch_args: # Launch host implementation - main(*launch_args) + host_main(*launch_args) else: # Show message box on_invalid_args(after_script_idx is None) From df1d0d4a864fce397ba02d2f009666e1e09ef2cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Mar 2024 18:32:55 +0100 Subject: [PATCH 35/37] fix unreal load plugins too --- .../hosts/unreal/plugins/load/load_alembic_animation.py | 2 +- client/ayon_core/hosts/unreal/plugins/load/load_layout.py | 2 +- .../ayon_core/hosts/unreal/plugins/load/load_layout_existing.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py index 02259b706c..64d684939c 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py @@ -72,7 +72,7 @@ class AnimationAlembicLoader(plugin.Loader): root = unreal_pipeline.AYON_ASSET_DIR folder_name = context["folder"]["name"] folder_path = context["folder"]["path"] - product_type = context["representation"]["context"]["family"] + product_type = context["product"]["productType"] suffix = "_CON" if folder_name: asset_name = "{}_{}".format(folder_name, name) diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py index 6c667d3d2f..6c01925453 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -659,7 +659,7 @@ class LayoutLoader(plugin.Loader): "loader": str(self.__class__.__name__), "representation": context["representation"]["id"], "parent": context["representation"]["versionId"], - "family": context["representation"]["context"]["family"], + "family": context["product"]["productType"], "loaded_assets": loaded_assets } imprint( diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py index 700b6957a2..56e36f6185 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py @@ -393,7 +393,7 @@ class ExistingLayoutLoader(plugin.Loader): folder_name = context["folder"]["name"] folder_path = context["folder"]["path"] - product_type = context["representation"]["context"]["family"] + product_type = context["product"]["productType"] asset_name = f"{folder_name}_{name}" if folder_name else name container_name = f"{folder_name}_{name}_CON" From d69ba8395c4898ddfdda157b5c436a3d10139db9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 00:50:40 +0100 Subject: [PATCH 36/37] Fix initial state for `CachedData.remapping` --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 034c90d27b..efa3bbf968 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -23,7 +23,7 @@ log = Logger.get_logger(__name__) class CachedData: - remapping = None + remapping = {} has_compatible_ocio_package = None config_version_data = {} ocio_config_colorspaces = {} From 0b36cbc5ec65e995ae6f82ee0044fce37968bada Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 09:15:15 +0100 Subject: [PATCH 37/37] Remove deprecated `AVALON_ACTIONS` --- client/ayon_core/modules/launcher_action.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/ayon_core/modules/launcher_action.py b/client/ayon_core/modules/launcher_action.py index 1faf6ef4b1..38e88d36ca 100644 --- a/client/ayon_core/modules/launcher_action.py +++ b/client/ayon_core/modules/launcher_action.py @@ -37,20 +37,6 @@ class LauncherAction(AYONAddon, ITrayAction): if path and os.path.exists(path): register_launcher_action_path(path) - paths_str = os.environ.get("AVALON_ACTIONS") or "" - if paths_str: - self.log.warning( - "WARNING: 'AVALON_ACTIONS' is deprecated. Support of this" - " environment variable will be removed in future versions." - " Please consider using 'OpenPypeModule' to define custom" - " action paths. Planned version to drop the support" - " is 3.17.2 or 3.18.0 ." - ) - - for path in paths_str.split(os.pathsep): - if path and os.path.exists(path): - register_launcher_action_path(path) - def on_action_trigger(self): """Implementation for ITrayAction interface.