diff --git a/client/ayon_deadline/plugins/publish/celaction/submit_celaction_deadline.py b/client/ayon_deadline/plugins/publish/celaction/submit_celaction_deadline.py index e9313e3f2f..7c77fc127f 100644 --- a/client/ayon_deadline/plugins/publish/celaction/submit_celaction_deadline.py +++ b/client/ayon_deadline/plugins/publish/celaction/submit_celaction_deadline.py @@ -1,13 +1,23 @@ import os import re -import json -import getpass import pyblish.api +import attr -from ayon_deadline.abstract_submit_deadline import requests_post +from ayon_deadline import abstract_submit_deadline -class CelactionSubmitDeadline(pyblish.api.InstancePlugin): +@attr.s +class CelactionPluginInfo(): + SceneFile = attr.ib(default=None) + OutputFilePath = attr.ib(default=None) + Output = attr.ib(default=None) + StartupDirectory = attr.ib(default=None) + Arguments = attr.ib(default=None) + ProjectPath = attr.ib(default=None) + AWSAssetFile0 = attr.ib(default=None) + + +class CelactionSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): """Submit CelAction2D scene to Deadline Renders are submitted to a Deadline Web Service. @@ -18,99 +28,37 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.1 hosts = ["celaction"] families = ["render.farm"] - settings_category = "deadline" - - deadline_department = "" - deadline_priority = 50 - deadline_pool = "" - deadline_pool_secondary = "" - deadline_group = "" - deadline_chunk_size = 1 - deadline_job_delay = "00:00:08:00" - def process(self, instance): + def get_job_info(self, job_info=None): + job_info.Plugin = "CelAction" - context = instance.context + # already collected explicit values for rendered Frames + if not job_info.Frames: + # Deadline requires integers in frame range + frame_range = "{}-{}".format( + int(round(self._instance.data["frameStart"])), + int(round(self._instance.data["frameEnd"]))) + job_info.Frames = frame_range - deadline_url = instance.data["deadline"]["url"] - assert deadline_url, "Requires Deadline Webservice URL" + return job_info - self.deadline_url = "{}/api/jobs".format(deadline_url) - self._comment = instance.data["comment"] - self._deadline_user = context.data.get( - "deadlineUser", getpass.getuser()) - self._frame_start = int(instance.data["frameStart"]) - self._frame_end = int(instance.data["frameEnd"]) + def get_plugin_info(self): + plugin_info = CelactionPluginInfo() + instance = self._instance - # get output path - render_path = instance.data['path'] - script_path = context.data["currentFile"] + render_path = instance.data["path"] + render_dir = os.path.dirname(render_path) - response = self.payload_submit(instance, - script_path, - render_path - ) - # Store output dir for unified publisher (filesequence) - instance.data["deadlineSubmissionJob"] = response.json() + self._expected_files(instance, render_path) - instance.data["outputDir"] = os.path.dirname( - render_path).replace("\\", "/") - - instance.data["publishJobState"] = "Suspended" - - # adding 2d render specific family for version identification in Loader - instance.data["families"] = ["render2d"] + script_path = self.scene_path + plugin_info.SceneFile = script_path + plugin_info.ProjectPath = script_path + plugin_info.OutputFilePath = render_dir.replace("\\", "/") + plugin_info.StartupDirectory = "" - def payload_submit(self, - instance, - script_path, - render_path - ): resolution_width = instance.data["resolutionWidth"] resolution_height = instance.data["resolutionHeight"] - render_dir = os.path.normpath(os.path.dirname(render_path)) - render_path = os.path.normpath(render_path) - script_name = os.path.basename(script_path) - - anatomy = instance.context.data["anatomy"] - publish_template = anatomy.get_template_item( - "publish", "default", "path" - ) - for item in instance.context: - if "workfile" in item.data["productType"]: - msg = "Workfile (scene) must be published along" - assert item.data["publish"] is True, msg - - template_data = item.data.get("anatomyData") - rep = item.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - template_filled = publish_template.format_strict( - template_data - ) - script_path = os.path.normpath(template_filled) - - self.log.info( - "Using published scene for render {}".format(script_path) - ) - - jobname = "%s - %s" % (script_name, instance.name) - - output_filename_0 = self.preview_fname(render_path) - - try: - # Ensure render folder exists - os.makedirs(render_dir) - except OSError: - pass - - # define chunk and priority - chunk_size = instance.context.data.get("chunk") - if not chunk_size: - chunk_size = self.deadline_chunk_size - - # search for %02d pattern in name, and padding number search_results = re.search(r"(%0)(\d)(d)[._]", render_path).groups() split_patern = "".join(search_results) padding_number = int(search_results[1]) @@ -128,126 +76,14 @@ def payload_submit(self, f"-= AbsoluteFrameNumber=on -= PadDigits={padding_number}", "-= ClearAttachment=on", ] + plugin_info.Arguments = " ".join(args) - payload = { - "JobInfo": { - # Job name, as seen in Monitor - "Name": jobname, - - # plugin definition - "Plugin": "CelAction", - - # Top-level group name - "BatchName": script_name, - - # Arbitrary username, for visualisation in Monitor - "UserName": self._deadline_user, - - "Department": self.deadline_department, - "Priority": self.deadline_priority, - - "Group": self.deadline_group, - "Pool": self.deadline_pool, - "SecondaryPool": self.deadline_pool_secondary, - "ChunkSize": chunk_size, - - "Frames": f"{self._frame_start}-{self._frame_end}", - "Comment": self._comment, - - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/"), - - # # Asset dependency to wait for at least - # the scene file to sync. - # "AssetDependency0": script_path - "ScheduledType": "Once", - "JobDelay": self.deadline_job_delay - }, - "PluginInfo": { - # Input - "SceneFile": script_path, - - # Output directory - "OutputFilePath": render_dir.replace("\\", "/"), - - # Plugin attributes - "StartupDirectory": "", - "Arguments": " ".join(args), - - # Resolve relative references - "ProjectPath": script_path, - "AWSAssetFile0": render_path, - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - plugin = payload["JobInfo"]["Plugin"] - self.log.debug("using render plugin : {}".format(plugin)) - - self.log.debug("Submitting..") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # adding expectied files to instance.data - self.expected_files(instance, render_path) - self.log.debug("__ expectedFiles: `{}`".format( - instance.data["expectedFiles"])) - auth = instance.data["deadline"]["auth"] - verify = instance.data["deadline"]["verify"] - response = requests_post(self.deadline_url, json=payload, - auth=auth, - verify=verify) - - if not response.ok: - self.log.error( - "Submission failed! [{}] {}".format( - response.status_code, response.content)) - self.log.debug(payload) - raise SystemExit(response.text) - - return response - - def preflight_check(self, instance): - """Ensure the startFrame, endFrame and byFrameStep are integers""" - - for key in ("frameStart", "frameEnd"): - value = instance.data[key] - - if int(value) == value: - continue - - self.log.warning( - "%f=%d was rounded off to nearest integer" - % (value, int(value)) - ) - - def preview_fname(self, path): - """Return output file path with #### for padding. - - Deadline requires the path to be formatted with # in place of numbers. - For example `/path/to/render.####.png` - - Args: - path (str): path to rendered images - - Returns: - str - - """ - self.log.debug("_ path: `{}`".format(path)) - if "%" in path: - search_results = re.search(r"[._](%0)(\d)(d)[._]", path).groups() - split_patern = "".join(search_results) - split_path = path.split(split_patern) - hashes = "#" * int(search_results[1]) - return "".join([split_path[0], hashes, split_path[-1]]) + # adding 2d render specific family for version identification in Loader + instance.data["families"] = ["render2d"] - self.log.debug("_ path: `{}`".format(path)) - return path + return attr.asdict(plugin_info) - def expected_files(self, instance, filepath): + def _expected_files(self, instance, filepath): """ Create expected files in instance data """ if not instance.data.get("expectedFiles"):