diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index 0a4755c166..500a0f9601 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -1024,6 +1024,18 @@ def script_name(): return nuke.root().knob("name").value() +def add_button_render_on_farm(node): + name = "renderOnFarm" + label = "Render On Farm" + value = ( + "from ayon_core.hosts.nuke.api.utils import submit_render_on_farm;" + "submit_render_on_farm(nuke.thisNode())" + ) + knob = nuke.PyScript_Knob(name, label, value) + knob.clearFlag(nuke.STARTLINE) + node.addKnob(knob) + + def add_button_write_to_read(node): name = "createReadNode" label = "Read From Rendered" @@ -1146,6 +1158,17 @@ def create_write_node( Return: node (obj): group node with avalon data as Knobs ''' + # Ensure name does not contain any invalid characters. + special_chars = re.escape("!@#$%^&*()=[]{}|\\;',.<>/?~+-") + special_chars_regex = re.compile(f"[{special_chars}]") + found_special_characters = list(special_chars_regex.findall(name)) + + msg = ( + f"Special characters found in name \"{name}\": " + f"{' '.join(found_special_characters)}" + ) + assert not found_special_characters, msg + prenodes = prenodes or [] # filtering variables @@ -1270,6 +1293,10 @@ def create_write_node( link.setFlag(0x1000) GN.addKnob(link) + # Adding render farm submission button. + if data.get("render_on_farm", False): + add_button_render_on_farm(GN) + # adding write to read button add_button_write_to_read(GN) diff --git a/client/ayon_core/hosts/nuke/api/utils.py b/client/ayon_core/hosts/nuke/api/utils.py index 1bfc1919fa..646bb0ece1 100644 --- a/client/ayon_core/hosts/nuke/api/utils.py +++ b/client/ayon_core/hosts/nuke/api/utils.py @@ -3,9 +3,15 @@ import re import nuke -from ayon_core import resources +import pyblish.util +import pyblish.api from qtpy import QtWidgets +from ayon_core import resources +from ayon_core.pipeline import registered_host +from ayon_core.tools.utils import show_message_dialog +from ayon_core.pipeline.create import CreateContext + def set_context_favorites(favorites=None): """ Adding favorite folders to nuke's browser @@ -142,3 +148,77 @@ def is_headless(): bool: headless """ return QtWidgets.QApplication.instance() is None + + +def submit_render_on_farm(node): + # Ensure code is executed in root context. + if nuke.root() == nuke.thisNode(): + _submit_render_on_farm(node) + else: + # If not in root context, move to the root context and then execute the + # code. + with nuke.root(): + _submit_render_on_farm(node) + + +def _submit_render_on_farm(node): + """Render on farm submission + + This function prepares the context for farm submission, validates it, + extracts relevant data, copies the current workfile to a timestamped copy, + and submits the job to the farm. + + Args: + node (Node): The node for which the farm submission is being made. + """ + + host = registered_host() + create_context = CreateContext(host) + + # Ensure CreateInstance is enabled. + for instance in create_context.instances: + if node.name() != instance.transient_data["node"].name(): + continue + + instance.data["active"] = True + + context = pyblish.api.Context() + context.data["create_context"] = create_context + # Used in pyblish plugin to determine which instance to publish. + context.data["node_name"] = node.name() + # Used in pyblish plugins to determine whether to run or not. + context.data["render_on_farm"] = True + + # Since we need to bypass version validation and incrementing, we need to + # remove the plugins from the list that are responsible for these tasks. + plugins = pyblish.api.discover() + blacklist = ["IncrementScriptVersion", "ValidateVersion"] + plugins = [ + plugin + for plugin in plugins + if plugin.__name__ not in blacklist + ] + + context = pyblish.util.publish(context, plugins=plugins) + + error_message = "" + success = True + for result in context.data["results"]: + if result["success"]: + continue + + success = False + + err = result["error"] + error_message += "\n" + error_message += err.formatted_traceback + + if not success: + show_message_dialog( + "Publish Errors", error_message, level="critical" + ) + return + + show_message_dialog( + "Submission Successful", "Submission to the farm was successful." + ) diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py b/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py index 770726e34f..fc2538f23d 100644 --- a/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py +++ b/client/ayon_core/hosts/nuke/plugins/create/create_write_image.py @@ -65,12 +65,16 @@ class CreateWriteImage(napi.NukeWriteCreator): ) def create_instance_node(self, product_name, instance_data): + settings = self.project_settings["nuke"]["create"]["CreateWriteImage"] # add fpath_template write_data = { "creator": self.__class__.__name__, "productName": product_name, - "fpath_template": self.temp_rendering_path_template + "fpath_template": self.temp_rendering_path_template, + "render_on_farm": ( + "render_on_farm" in settings["instance_attributes"] + ) } write_data.update(instance_data) diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py b/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py index 96ac2fac9c..47796d159c 100644 --- a/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py +++ b/client/ayon_core/hosts/nuke/plugins/create/create_write_prerender.py @@ -46,11 +46,17 @@ class CreateWritePrerender(napi.NukeWriteCreator): return attr_defs def create_instance_node(self, product_name, instance_data): + settings = self.project_settings["nuke"]["create"] + settings = settings["CreateWritePrerender"] + # add fpath_template write_data = { "creator": self.__class__.__name__, "productName": product_name, - "fpath_template": self.temp_rendering_path_template + "fpath_template": self.temp_rendering_path_template, + "render_on_farm": ( + "render_on_farm" in settings["instance_attributes"] + ) } write_data.update(instance_data) diff --git a/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py b/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py index 24bddb3d26..4cb5ccdfa2 100644 --- a/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py +++ b/client/ayon_core/hosts/nuke/plugins/create/create_write_render.py @@ -40,11 +40,16 @@ class CreateWriteRender(napi.NukeWriteCreator): return attr_defs def create_instance_node(self, product_name, instance_data): + settings = self.project_settings["nuke"]["create"]["CreateWriteRender"] + # add fpath_template write_data = { "creator": self.__class__.__name__, "productName": product_name, - "fpath_template": self.temp_rendering_path_template + "fpath_template": self.temp_rendering_path_template, + "render_on_farm": ( + "render_on_farm" in settings["instance_attributes"] + ) } write_data.update(instance_data) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_headless_farm.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_headless_farm.py new file mode 100644 index 0000000000..3f49a2bf01 --- /dev/null +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_headless_farm.py @@ -0,0 +1,56 @@ +import pyblish.api + +from ayon_core.pipeline.publish import ( + AYONPyblishPluginMixin +) + + +class CollectRenderOnFarm(pyblish.api.ContextPlugin): + """Setup instances for render on farm submission.""" + + # Needs to be after CollectFromCreateContext + order = pyblish.api.CollectorOrder - 0.49 + label = "Collect Render On Farm" + hosts = ["nuke"] + + def process(self, context): + if not context.data.get("render_on_farm", False): + return + + for instance in context: + if instance.data["family"] == "workfile": + instance.data["active"] = False + continue + + # Filter out all other instances. + node = instance.data["transientData"]["node"] + if node.name() != instance.context.data["node_name"]: + instance.data["active"] = False + continue + + instance.data["families"].append("render_on_farm") + + # Enable for farm publishing. + instance.data["farm"] = True + + # Skip workfile version incremental save. + instance.context.data["increment_script_version"] = False + + +class SetupRenderOnFarm(pyblish.api.InstancePlugin, AYONPyblishPluginMixin): + """Setup instance for render on farm submission.""" + + order = pyblish.api.CollectorOrder + 0.4999 + label = "Setup Render On Farm" + hosts = ["nuke"] + families = ["render_on_farm"] + + def process(self, instance): + # Clear the families as we only want the main family, ei. no review + # etc. + instance.data["families"] = ["render_on_farm"] + + # Use the workfile instead of published. + publish_attributes = instance.data["publish_attributes"] + plugin_attributes = publish_attributes["NukeSubmitDeadline"] + plugin_attributes["use_published_workfile"] = False diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_headless_farm.py b/client/ayon_core/hosts/nuke/plugins/publish/extract_headless_farm.py new file mode 100644 index 0000000000..4ba55f8c46 --- /dev/null +++ b/client/ayon_core/hosts/nuke/plugins/publish/extract_headless_farm.py @@ -0,0 +1,36 @@ +import os +from datetime import datetime +import shutil + +import pyblish.api + +from ayon_core.pipeline import registered_host + + +class ExtractRenderOnFarm(pyblish.api.InstancePlugin): + """Copy the workfile to a timestamped copy.""" + + order = pyblish.api.ExtractorOrder + 0.499 + label = "Extract Render On Farm" + hosts = ["nuke"] + families = ["render_on_farm"] + + def process(self, instance): + if not instance.context.data.get("render_on_farm", False): + return + + host = registered_host() + current_datetime = datetime.now() + formatted_timestamp = current_datetime.strftime("%Y%m%d%H%M%S") + base, ext = os.path.splitext(host.current_file()) + + directory = os.path.join(os.path.dirname(base), "farm_submissions") + if not os.path.exists(directory): + os.makedirs(directory) + + filename = "{}_{}{}".format( + os.path.basename(base), formatted_timestamp, ext + ) + path = os.path.join(directory, filename).replace("\\", "/") + instance.context.data["currentFile"] = path + shutil.copy(host.current_file(), path) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py b/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py index 6b0be42ba1..70fd04a985 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/increment_script_version.py @@ -13,6 +13,8 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): hosts = ['nuke'] def process(self, context): + if not context.data.get("increment_script_version", True): + return assert all(result["success"] for result in context.data["results"]), ( "Publishing not successful so version is not increased.") diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 6923c2b16b..2592d358e5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -26,27 +26,32 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, order = pyblish.api.CollectorOrder + 0.420 label = "Collect Deadline Pools" - hosts = ["aftereffects", - "fusion", - "harmony" - "nuke", - "maya", - "max", - "houdini"] + hosts = [ + "aftereffects", + "fusion", + "harmony", + "maya", + "max", + "houdini", + "nuke", + ] - families = ["render", - "rendering", - "render.farm", - "renderFarm", - "renderlayer", - "maxrender", - "usdrender", - "redshift_rop", - "arnold_rop", - "mantra_rop", - "karma_rop", - "vray_rop", - "publish.hou"] + families = [ + "render", + "prerender", + "rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou", + ] primary_pool = None secondary_pool = None diff --git a/client/ayon_core/plugins/publish/validate_version.py b/client/ayon_core/plugins/publish/validate_version.py index 9031194e8c..25a5757330 100644 --- a/client/ayon_core/plugins/publish/validate_version.py +++ b/client/ayon_core/plugins/publish/validate_version.py @@ -1,8 +1,10 @@ import pyblish.api -from ayon_core.pipeline.publish import PublishValidationError +from ayon_core.pipeline.publish import ( + PublishValidationError, OptionalPyblishPluginMixin +) -class ValidateVersion(pyblish.api.InstancePlugin): +class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validate instance version. AYON does not allow overwriting previously published versions. @@ -18,6 +20,9 @@ class ValidateVersion(pyblish.api.InstancePlugin): active = True def process(self, instance): + if not self.is_active(instance.data): + return + version = instance.data.get("version") latest_version = instance.data.get("latestVersion") diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py index bc166bd14e..d8decef208 100644 --- a/server_addon/nuke/package.py +++ b/server_addon/nuke/package.py @@ -1,3 +1,3 @@ name = "nuke" title = "Nuke" -version = "0.1.13" +version = "0.1.14" diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 6bdc5ee5ad..e4a0f9c938 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -12,7 +12,11 @@ def instance_attributes_enum(): return [ {"value": "reviewable", "label": "Reviewable"}, {"value": "farm_rendering", "label": "Farm rendering"}, - {"value": "use_range_limit", "label": "Use range limit"} + {"value": "use_range_limit", "label": "Use range limit"}, + { + "value": "render_on_farm", + "label": "Render On Farm" + } ]