diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 9091b24150..de764e3b33 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -34,6 +34,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): ] def process(self, instance): + instance.data["toBeRenderedOn"] = "deadline" context = instance.context DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL") diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index e5deb1b070..0dd34ba713 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -22,7 +22,7 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): families = ["saver.deadline"] def process(self, instance): - + instance.data["toBeRenderedOn"] = "deadline" context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 88550cac8b..3053c80b11 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -233,7 +233,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): return (metadata_path, roothless_mtdt_p) - def _submit_deadline_post_job(self, instance, job): + def _submit_deadline_post_job(self, instance, job, instances): """Submit publish job to Deadline. Deadline specific code separated from :meth:`process` for sake of @@ -253,7 +253,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "Plugin": "Python", "BatchName": job["Props"]["Batch"], "Name": job_name, - "JobDependency0": job["_id"], "UserName": job["Props"]["User"], "Comment": instance.context.data.get("comment", ""), @@ -276,12 +275,33 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Mandatory for Deadline, may be empty "AuxFiles": [], } + """ + In this part we will add file dependencies instead of job dependencies. + This way we don't need to take care of tile assembly job, getting its + id or name. We expect it to produce specific file with specific name + and we are just waiting for them. + """ + if instance.data.get("tileRendering"): + self.log.info("Adding tile assembly results as dependencies...") + asset_index = 0 + for inst in instances: + for represenation in inst.get("representations", []): + if isinstance(represenation["files"], (list, tuple)): + for file in represenation["files"]: + dependency = os.path.join(output_dir, file) + payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 + else: + dependency = os.path.join( + output_dir, represenation["files"]) + payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 + asset_index += 1 + else: + payload["JobInfo"]["JobDependency0"] = job["_id"] # Transfer the environment from the original job to this dependent # job so they use the same environment metadata_path, roothless_metadata_path = self._create_metadata_path( instance) - environment = job["Props"].get("Env", {}) environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] @@ -422,7 +442,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # but we really expect only one collection. # Nothing else make sense. assert len(cols) == 1, "only one image sequence type is expected" # noqa: E501 - _, ext = os.path.splitext(cols[0].tail) + ext = cols[0].tail.lstrip(".") col = list(cols[0]) self.log.debug(col) @@ -628,25 +648,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if hasattr(instance, "_log"): data['_log'] = instance._log - render_job = data.pop("deadlineSubmissionJob", None) - submission_type = "deadline" - if not render_job: - # No deadline job. Try Muster: musterSubmissionJob - render_job = data.pop("musterSubmissionJob", None) - submission_type = "muster" - assert render_job, ( - "Can't continue without valid Deadline " - "or Muster submission prior to this " - "plug-in." - ) - - if submission_type == "deadline": - self.DEADLINE_REST_URL = os.environ.get( - "DEADLINE_REST_URL", "http://localhost:8082" - ) - assert self.DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - - self._submit_deadline_post_job(instance, render_job) asset = data.get("asset") or api.Session["AVALON_ASSET"] subset = data.get("subset") @@ -861,6 +862,61 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): at.get("subset"), at.get("version"))) instances = new_instances + r''' SUBMiT PUBLiSH JOB 2 D34DLiN3 + ____ + ' ' .---. .---. .--. .---. .--..--..--..--. .---. + | | --= \ | . \/ _|/ \| . \ || || \ |/ _| + | JOB | --= / | | || __| .. | | | |;_ || \ || __| + | | |____./ \.__|._||_.|___./|_____|||__|\__|\.___| + ._____. + + ''' + + render_job = None + if instance.data.get("toBeRenderedOn") == "deadline": + render_job = data.pop("deadlineSubmissionJob", None) + submission_type = "deadline" + + if instance.data.get("toBeRenderedOn") == "muster": + render_job = data.pop("musterSubmissionJob", None) + submission_type = "muster" + + if not render_job and instance.data.get("tileRendering") is False: + raise AssertionError(("Cannot continue without valid Deadline " + "or Muster submission.")) + + if not render_job: + import getpass + + render_job = {} + self.log.info("Faking job data ...") + render_job["Props"] = {} + # Render job doesn't exist because we do not have prior submission. + # We still use data from it so lets fake it. + # + # Batch name reflect original scene name + render_job["Props"]["Batch"] = os.path.splitext(os.path.basename( + context.data.get("currentFile")))[0] + # User is deadline user + render_job["Props"]["User"] = context.data.get( + "deadlineUser", getpass.getuser()) + # Priority is now not handled at all + render_job["Props"]["Pri"] = instance.data.get("priority") + + render_job["Props"]["Env"] = { + "FTRACK_API_USER": os.environ.get("FTRACK_API_USER"), + "FTRACK_API_KEY": os.environ.get("FTRACK_API_KEY"), + "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), + } + + if submission_type == "deadline": + self.DEADLINE_REST_URL = os.environ.get( + "DEADLINE_REST_URL", "http://localhost:8082" + ) + assert self.DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + self._submit_deadline_post_job(instance, render_job, instances) + # publish job file publish_job = { "asset": asset, @@ -872,7 +928,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "version": context.data["version"], # this is workfile version "intent": context.data.get("intent"), "comment": context.data.get("comment"), - "job": render_job, + "job": render_job or None, "session": api.Session.copy(), "instances": instances } diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 3b2048d8f0..9e5f9310ae 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -40,6 +40,9 @@ class CreateRender(avalon.maya.Creator): vrscene (bool): Submit as ``vrscene`` file for standalone V-Ray renderer. ass (bool): Submit as ``ass`` file for standalone Arnold renderer. + tileRendering (bool): Instance is set to tile rendering mode. We + won't submit actuall render, but we'll make publish job to wait + for Tile Assemly job done and then publish. See Also: https://pype.club/docs/artist_hosts_maya#creating-basic-render-setup @@ -181,6 +184,7 @@ class CreateRender(avalon.maya.Creator): self.data["machineList"] = "" self.data["useMayaBatch"] = False self.data["vrayScene"] = False + self.data["tileRendering"] = False # Disable for now as this feature is not working yet # self.data["assScene"] = False @@ -189,8 +193,8 @@ class CreateRender(avalon.maya.Creator): def _load_credentials(self): """Load Muster credentials. - Load Muster credentials from file and set ```MUSTER_USER``, - ```MUSTER_PASSWORD``, ``MUSTER_REST_URL`` is loaded from presets. + Load Muster credentials from file and set ``MUSTER_USER``, + ``MUSTER_PASSWORD``, ``MUSTER_REST_URL`` is loaded from presets. Raises: RuntimeError: If loaded credentials are invalid. diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 03b14f76bb..5ca9392080 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -242,6 +242,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "resolutionWidth": cmds.getAttr("defaultResolution.width"), "resolutionHeight": cmds.getAttr("defaultResolution.height"), "pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"), + "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 + "priority": render_instance.data.get("priority") } # Apply each user defined attribute as data diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index d5500f7aa8..7fe20c779d 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -45,6 +45,7 @@ payload_skeleton = { "Plugin": "MayaPype", "Frames": "{start}-{end}x{step}", "Comment": None, + "Priority": 50, }, "PluginInfo": { "SceneFile": None, # Input @@ -86,7 +87,8 @@ def get_renderer_variables(renderlayer, root): gin="#" * int(padding), lut=True, layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = filename_0.replace('_', '_beauty') + filename_0 = re.sub('_', '_beauty', + filename_0, flags=re.IGNORECASE) prefix_attr = "defaultRenderGlobals.imageFilePrefix" if renderer == "vray": renderlayer = renderlayer.split("_")[-1] @@ -165,6 +167,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" + instance.data["toBeRenderedOn"] = "deadline" self._instance = instance self._deadline_url = os.environ.get( "DEADLINE_REST_URL", "http://localhost:8082") @@ -173,6 +176,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context workspace = context.data["workspaceDir"] anatomy = context.data['anatomy'] + instance.data["toBeRenderedOn"] = "deadline" filepath = None @@ -299,6 +303,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload_skeleton["JobInfo"]["Name"] = jobname # Arbitrary username, for visualisation in Monitor payload_skeleton["JobInfo"]["UserName"] = deadline_user + # Set job priority + payload_skeleton["JobInfo"]["Priority"] = self._instance.data.get( + "priority", 50) # Optional, enable double-click to preview rendered # frames from Deadline Monitor payload_skeleton["JobInfo"]["OutputDirectory0"] = \ @@ -386,7 +393,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 output_file = rem[0] else: - output_file = col.format('{head}{padding}{tail}') + output_file = col[0].format('{head}{padding}{tail}') payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 output_filenames[exp_index] = output_file exp_index += 1 @@ -400,7 +407,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "with them.") payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 else: - output_file = col.format('{head}{padding}{tail}') + output_file = col[0].format('{head}{padding}{tail}') payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 plugin = payload["JobInfo"]["Plugin"] @@ -409,18 +416,21 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.preflight_check(instance) # Submit job to farm ------------------------------------------------ - self.log.info("Submitting ...") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) + if not instance.data.get("tileRendering"): + self.log.info("Submitting ...") + self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(self._deadline_url) - response = self._requests_post(url, json=payload) - if not response.ok: - raise Exception(response.text) + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(self._deadline_url) + response = self._requests_post(url, json=payload) + if not response.ok: + raise Exception(response.text) + instance.data["deadlineSubmissionJob"] = response.json() + else: + self.log.info("Skipping submission, tile rendering enabled.") # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - instance.data["deadlineSubmissionJob"] = response.json() def _get_maya_payload(self, data): payload = copy.deepcopy(payload_skeleton) diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index 5a2e578793..ffe434048a 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -249,6 +249,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): Authenticate with Muster, collect all data, prepare path for post render publish job and submit job to farm. """ + instance.data["toBeRenderedOn"] = "muster" # setup muster environment self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") diff --git a/pype/plugins/maya/publish/validate_deadline_tile_submission.py b/pype/plugins/maya/publish/validate_deadline_tile_submission.py new file mode 100644 index 0000000000..b0b995de3e --- /dev/null +++ b/pype/plugins/maya/publish/validate_deadline_tile_submission.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +"""Validate settings from Deadline Submitter. + +This is useful mainly for tile rendering, where jobs on farm are created by +submitter script from Maya. + +Unfortunately Deadline doesn't expose frame number for tiles job so that +cannot be validated, even if it is important setting. Also we cannot +determine if 'Region Rendering' (tile rendering) is enabled or not because +of the same thing. + +""" +import os + +from maya import mel +from maya import cmds + +import pyblish.api +from pype.hosts.maya import lib + + +class ValidateDeadlineTileSubmission(pyblish.api.InstancePlugin): + """Validate Deadline Submission settings are OK for tile rendering.""" + + label = "Validate Deadline Tile Submission" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["renderlayer"] + if not os.environ.get("DEADLINE_REST_URL"): + active = False + + def process(self, instance): + """Entry point.""" + # try if Deadline submitter was loaded + if mel.eval("exists SubmitJobToDeadline") == 0: + # if not, try to load it manually + try: + mel.eval("source DeadlineMayaClient;") + except RuntimeError: + raise AssertionError("Deadline Maya client cannot be loaded") + mel.eval("DeadlineMayaClient();") + assert mel.eval("exists SubmitJobToDeadline") == 1, ( + "Deadline Submission script cannot be initialized.") + if instance.data.get("tileRendering"): + job_name = cmds.getAttr("defaultRenderGlobals.deadlineJobName") + scene_name = os.path.splitext(os.path.basename( + instance.context.data.get("currentFile")))[0] + if job_name != scene_name: + self.log.warning(("Job submitted through Deadline submitter " + "has different name then current scene " + "{} / {}").format(job_name, scene_name)) + if cmds.getAttr("defaultRenderGlobals.deadlineTileSingleJob") == 1: + layer = instance.data['setMembers'] + anim_override = lib.get_attr_in_layer( + "defaultRenderGlobals.animation", layer=layer) + assert anim_override, ( + "Animation must be enabled in " + "Render Settings even when rendering single frame." + ) + + start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") + end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") + assert start_frame == end_frame, ( + "Start frame and end frame are not equals. When " + "'Submit All Tles As A Single Job' is selected, only " + "single frame is expected to be rendered. It must match " + "the one specified in Deadline Submitter under " + "'Region Rendering'" + ) diff --git a/pype/plugins/maya/publish/validate_frame_range.py b/pype/plugins/maya/publish/validate_frame_range.py index 0d51a83cf5..1ee6e2bd25 100644 --- a/pype/plugins/maya/publish/validate_frame_range.py +++ b/pype/plugins/maya/publish/validate_frame_range.py @@ -29,6 +29,12 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context + if instance.data.get("tileRendering"): + self.log.info(( + "Skipping frame range validation because " + "tile rendering is enabled." + )) + return frame_start_handle = int(context.data.get("frameStartHandle")) frame_end_handle = int(context.data.get("frameEndHandle")) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 26d3f9b571..2c7d468d3a 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -28,6 +28,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): deadline_chunk_size = 1 def process(self, instance): + instance.data["toBeRenderedOn"] = "deadline" families = instance.data["families"] node = instance[0]