From 0a7bad7b386fe417a03552c7081980603e6b55af Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jan 2021 15:10:46 +0100 Subject: [PATCH 01/14] Harmony to Deadline - 2x.develop version --- pype/hosts/harmony/__init__.py | 35 +- pype/hosts/harmony/js/PypeHarmony.js | 18 +- .../harmony/js/publish/CollectFarmRender.js | 52 +++ .../global/publish/submit_publish_job.py | 5 +- .../harmony/create/create_farm_render.py | 31 ++ pype/plugins/harmony/create/create_render.py | 4 +- .../harmony/publish/collect_farm_render.py | 170 ++++++++ .../harmony/publish/collect_instances.py | 4 + .../harmony/publish/collect_palettes.py | 1 + pype/plugins/harmony/publish/collect_scene.py | 15 + .../harmony/publish/collect_workfile.py | 2 +- .../plugins/harmony/publish/extract_render.py | 2 +- .../publish/submit_harmony_deadline..py | 404 ++++++++++++++++++ .../publish/validate_scene_settings.py | 2 +- 14 files changed, 732 insertions(+), 13 deletions(-) create mode 100644 pype/hosts/harmony/js/publish/CollectFarmRender.js create mode 100644 pype/plugins/harmony/create/create_farm_render.py create mode 100644 pype/plugins/harmony/publish/collect_farm_render.py create mode 100644 pype/plugins/harmony/publish/submit_harmony_deadline..py diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 7ea261292e..a3de979839 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -50,10 +50,19 @@ def get_asset_settings(): } try: - skip_resolution_check = \ - config.get_presets()["harmony"]["general"]["skip_resolution_check"] - skip_timelines_check = \ - config.get_presets()["harmony"]["general"]["skip_timelines_check"] + # temporary, in pype3 replace with api.get_current_project_settings + skip_resolution_check = ( + get_current_project_settings() + ["harmony"] + ["general"] + ["skip_resolution_check"] + ) + skip_timelines_check = ( + get_current_project_settings() + ["harmony"] + ["general"] + ["skip_timelines_check"] + ) except KeyError: skip_resolution_check = [] skip_timelines_check = [] @@ -69,6 +78,24 @@ def get_asset_settings(): return scene_data +# temporary, in pype3 replace with api.get_current_project_settings +def get_current_project_settings(): + """Project settings for current context project. + + Project name should be stored in environment variable `AVALON_PROJECT`. + This function should be used only in host context where environment + variable must be set and should not happen that any part of process will + change the value of the enviornment variable. + """ + project_name = os.environ.get("AVALON_PROJECT") + if not project_name: + raise ValueError( + "Missing context project in environemt variable `AVALON_PROJECT`." + ) + presets = config.get_presets(project=os.environ['AVALON_PROJECT']) + return presets + + def ensure_scene_settings(): """Validate if Harmony scene has valid settings.""" settings = get_asset_settings() diff --git a/pype/hosts/harmony/js/PypeHarmony.js b/pype/hosts/harmony/js/PypeHarmony.js index a98dbd52cd..555a5ce8fd 100644 --- a/pype/hosts/harmony/js/PypeHarmony.js +++ b/pype/hosts/harmony/js/PypeHarmony.js @@ -5,7 +5,7 @@ var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH'); include(LD_OPENHARMONY_PATH + '/openHarmony.js'); -this.__proto__['$'] = $; + /** @@ -79,7 +79,8 @@ PypeHarmony.getSceneSettings = function() { scene.getStopFrame(), sound.getSoundtrackAll().path(), scene.defaultResolutionX(), - scene.defaultResolutionY() + scene.defaultResolutionY(), + scene.defaultResolutionFOV() ]; }; @@ -200,3 +201,16 @@ PypeHarmony.getDependencies = function(_node) { } return dependencies; }; + + +/** + * return version of running Harmony instance. + * @function + * @return {array} [major_version, minor_version] + */ +PypeHarmony.getVersion = function() { + return [ + about.getMajorVersion(), + about.getMinorVersion() + ]; +} diff --git a/pype/hosts/harmony/js/publish/CollectFarmRender.js b/pype/hosts/harmony/js/publish/CollectFarmRender.js new file mode 100644 index 0000000000..153c38e868 --- /dev/null +++ b/pype/hosts/harmony/js/publish/CollectFarmRender.js @@ -0,0 +1,52 @@ +/* global PypeHarmony:writable, include */ +// *************************************************************************** +// * CollectFarmRender * +// *************************************************************************** + + +// check if PypeHarmony is defined and if not, load it. +if (typeof PypeHarmony !== 'undefined') { + var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS'); + include(PYPE_HARMONY_JS + '/pype_harmony.js'); +} + + +/** + * @namespace + * @classdesc Image Sequence loader JS code. + */ +var CollectFarmRender = function() {}; + + +/** + * Get information important for render output. + * @function + * @param node {String} node name. + * @return {array} array of render info. + * + * @example + * + * var ret = [ + * file_prefix, // like foo/bar- + * type, // PNG4, ... + * leading_zeros, // 3 - for 0001 + * start // start frame + * ] + */ +CollectFarmRender.prototype.getRenderNodeSettings = function(n) { + // this will return + var output = [ + node.getTextAttr( + n, frame.current(), 'DRAWING_NAME'), + node.getTextAttr( + n, frame.current(), 'DRAWING_TYPE'), + node.getTextAttr( + n, frame.current(), 'LEADING_ZEROS'), + node.getTextAttr(n, frame.current(), 'START') + ]; + + return output; +}; + +// add self to Pype Loaders +PypeHarmony.Publish.CollectFarmRender = new CollectFarmRender(); diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index b8bf240c06..be2add4ed0 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -128,13 +128,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" - hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects"] + hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] - families = ["render.farm", "prerener", + families = ["render.farm", "prerender", "renderlayer", "imagesequence", "vrayscene"] aov_filter = {"maya": [r".+(?:\.|_)([Bb]eauty)(?:\.|_).*"], "aftereffects": [r".*"], # for everything from AE + "harmony": [r".*"], # for everything from AE "celaction": [r".*"]} enviro_filter = [ diff --git a/pype/plugins/harmony/create/create_farm_render.py b/pype/plugins/harmony/create/create_farm_render.py new file mode 100644 index 0000000000..e134f28f43 --- /dev/null +++ b/pype/plugins/harmony/create/create_farm_render.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +"""Create Composite node for render on farm.""" +from avalon import harmony + + +class CreateFarmRender(harmony.Creator): + """Composite node for publishing renders.""" + + name = "renderDefault" + label = "Render on Farm" + family = "renderFarm" + node_type = "WRITE" + + def __init__(self, *args, **kwargs): + """Constructor.""" + super(CreateFarmRender, self).__init__(*args, **kwargs) + + def setup_node(self, node): + """Set render node.""" + path = "render/{0}/{0}.".format(node.split("/")[-1]) + harmony.send( + { + "function": f"PypeHarmony.Creators.CreateRender.create", + "args": [node, path] + }) + harmony.send( + { + "function": f"PypeHarmony.color", + "args": [[0.9, 0.75, 0.3, 1.0]] + } + ) diff --git a/pype/plugins/harmony/create/create_render.py b/pype/plugins/harmony/create/create_render.py index f8fc2d238b..8e3408d900 100644 --- a/pype/plugins/harmony/create/create_render.py +++ b/pype/plugins/harmony/create/create_render.py @@ -8,7 +8,7 @@ class CreateRender(harmony.Creator): name = "renderDefault" label = "Render" - family = "render" + family = "renderLocal" node_type = "WRITE" def __init__(self, *args, **kwargs): @@ -18,7 +18,7 @@ class CreateRender(harmony.Creator): def setup_node(self, node): """Set render node.""" self_name = self.__class__.__name__ - path = "{0}/{0}".format(node.split("/")[-1]) + path = "render/{0}/{0}.".format(node.split("/")[-1]) harmony.send( { "function": f"PypeHarmony.Creators.{self_name}.create", diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py new file mode 100644 index 0000000000..baf037491b --- /dev/null +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +"""Collect data to render from scene.""" +from pathlib import Path + +import attr +from avalon import harmony, api + +import pype.lib.abstract_collect_render +from pype.lib.abstract_collect_render import RenderInstance + + +@attr.s +class HarmonyRenderInstance(RenderInstance): + outputType = attr.ib(default="Image") + outputFormat = attr.ib(default="PNG4") + outputStartFrame = attr.ib(default=1) + leadingZeros = attr.ib(default=3) + + +class CollectFarmRender(pype.lib.abstract_collect_render. + AbstractCollectRender): + """Gather all publishable renders.""" + + # https://docs.toonboom.com/help/harmony-17/premium/reference/node/output/write-node-image-formats.html + ext_mapping = { + "tvg": ["TVG"], + "tga": ["TGA", "TGA4", "TGA3", "TGA1"], + "sgi": ["SGI", "SGI4", "SGA3", "SGA1", "SGIDP", "SGIDP4", "SGIDP3"], + "psd": ["PSD", "PSD1", "PSD3", "PSD4", "PSDDP", "PSDDP1", "PSDDP3", + "PSDDP4"], + "yuv": ["YUV"], + "pal": ["PAL"], + "scan": ["SCAN"], + "png": ["PNG", "PNG4", "PNGDP", "PNGDP3", "PNGDP4"], + "jpg": ["JPG"], + "bmp": ["BMP", "BMP4"], + "opt": ["OPT", "OPT1", "OPT3", "OPT4"], + "var": ["VAR"], + "tif": ["TIF"], + "dpx": ["DPX", "DPX3_8", "DPX3_10", "DPX3_12", "DPX3_16", + "DPX3_10_INVERTED_CHANNELS", "DPX3_12_INVERTED_CHANNELS", + "DPX3_16_INVERTED_CHANNELS"], + "exr": ["EXR"], + "pdf": ["PDF"], + "dtext": ["DTEX"] + } + + def get_expected_files(self, render_instance): + """Get list of expected files to be rendered from Harmony. + + This returns full path with file name determined by Write node + settings. + """ + start = render_instance.frameStart + end = render_instance.frameEnd + node = render_instance.setMembers[0] + self_name = self.__class__.__name__ + # 0 - filename / 1 - type / 2 - zeros / 3 - start + info = harmony.send( + { + "function": f"PypeHarmony.Publish.{self_name}." + "getRenderNodeSettings", + "args": node + })["result"] + + ext = None + for k, v in self.ext_mapping.items(): + if info[1] in v: + ext = k + + if not ext: + raise AssertionError( + f"Cannot determine file extension for {info[1]}") + + path = Path(render_instance.source).parent + + # is sequence start node on write node offsetting whole sequence? + expected_files = [] + for frame in range(start, end): + expected_files.append( + path / "{}{}.{}".format( + info[0], + str(frame).rjust(int(info[2]) + 1, "0"), + ext + ) + ) + + return expected_files + + def get_instances(self, context): + """Get instances per Write node in `renderFarm` family.""" + version = None + if self.sync_workfile_version: + version = context.data["version"] + + instances = [] + + self_name = self.__class__.__name__ + + for node in context.data["allNodes"]: + data = harmony.read(node) + + # Skip non-tagged nodes. + if not data: + continue + + # Skip containers. + if "container" in data["id"]: + continue + + if data["family"] != "renderFarm": + continue + + # 0 - filename / 1 - type / 2 - zeros / 3 - start + info = harmony.send( + { + "function": f"PypeHarmony.Publish.{self_name}." + "getRenderNodeSettings", + "args": node + })["result"] + + # TODO: handle pixel aspect and frame step + # TODO: set Deadline stuff (pools, priority, etc. by presets) + render_instance = HarmonyRenderInstance( + version=version, + time=api.time(), + source=context.data["currentFile"], + label=node.split("/")[1], + subset=node.split("/")[1], + asset=api.Session["AVALON_ASSET"], + attachTo=False, + setMembers=[node], + publish=True, + review=False, + renderer=None, + priority=50, + name=node.split("/")[1], + + family="renderlayer", + families=["renderlayer"], + + resolutionWidth=context.data["resolutionWidth"], + resolutionHeight=context.data["resolutionHeight"], + pixelAspect=1.0, + multipartExr=False, + tileRendering=False, + tilesX=0, + tilesY=0, + convertToScanline=False, + + # time settings + frameStart=context.data["frameStart"], + frameEnd=context.data["frameEnd"], + frameStep=1, + outputType="Image", + outputFormat=info[1], + outputStartFrame=info[3], + leadingZeros=info[2], + toBeRenderedOn='deadline' + + ) + self.log.debug(render_instance) + instances.append(render_instance) + + return instances + + def add_additional_data(self, instance): + instance["FOV"] = self._context.data["FOV"] + + return instance diff --git a/pype/plugins/harmony/publish/collect_instances.py b/pype/plugins/harmony/publish/collect_instances.py index 21aa00e972..c3e551271f 100644 --- a/pype/plugins/harmony/publish/collect_instances.py +++ b/pype/plugins/harmony/publish/collect_instances.py @@ -49,6 +49,10 @@ class CollectInstances(pyblish.api.ContextPlugin): if "container" in data["id"]: continue + # skip render farm family as it is collected separately + if data["family"] == "renderFarm": + continue + instance = context.create_instance(node.split("/")[-1]) instance.append(node) instance.data.update(data) diff --git a/pype/plugins/harmony/publish/collect_palettes.py b/pype/plugins/harmony/publish/collect_palettes.py index 0900f3c3ff..26b83ff171 100644 --- a/pype/plugins/harmony/publish/collect_palettes.py +++ b/pype/plugins/harmony/publish/collect_palettes.py @@ -27,6 +27,7 @@ class CollectPalettes(pyblish.api.ContextPlugin): instance.data.update({ "id": id, "family": "harmony.palette", + 'families': [], "asset": os.environ["AVALON_ASSET"], "subset": "{}{}".format("palette", name) }) diff --git a/pype/plugins/harmony/publish/collect_scene.py b/pype/plugins/harmony/publish/collect_scene.py index 2a838ca0cd..afb49369dc 100644 --- a/pype/plugins/harmony/publish/collect_scene.py +++ b/pype/plugins/harmony/publish/collect_scene.py @@ -30,9 +30,24 @@ class CollectScene(pyblish.api.ContextPlugin): context.data["audioPath"] = result[6] context.data["resolutionWidth"] = result[7] context.data["resolutionHeight"] = result[8] + context.data["FOV"] = result[9] all_nodes = harmony.send( {"function": "node.subNodes", "args": ["Top"]} )["result"] context.data["allNodes"] = all_nodes + + # collect all write nodes to be able disable them in Deadline + all_write_nodes = harmony.send( + {"function": "node.getNodes", "args": ["WRITE"]} + )["result"] + + context.data["all_write_nodes"] = all_write_nodes + + result = harmony.send( + { + f"function": "PypeHarmony.getVersion", + "args": []} + )["result"] + context.data["harmonyVersion"] = "{}.{}".format(result[0], result[1]) diff --git a/pype/plugins/harmony/publish/collect_workfile.py b/pype/plugins/harmony/publish/collect_workfile.py index 02eb38db48..63bfd5929b 100644 --- a/pype/plugins/harmony/publish/collect_workfile.py +++ b/pype/plugins/harmony/publish/collect_workfile.py @@ -26,7 +26,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): "label": basename, "name": basename, "family": family, - "families": [], + "families": [family], "representations": [], "asset": os.environ["AVALON_ASSET"] }) diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index c5744d43e2..84fa503f54 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -17,7 +17,7 @@ class ExtractRender(pyblish.api.InstancePlugin): label = "Extract Render" order = pyblish.api.ExtractorOrder hosts = ["harmony"] - families = ["render"] + families = ["renderLocal"] def process(self, instance): # Collect scene data. diff --git a/pype/plugins/harmony/publish/submit_harmony_deadline..py b/pype/plugins/harmony/publish/submit_harmony_deadline..py new file mode 100644 index 0000000000..88ab1bec1d --- /dev/null +++ b/pype/plugins/harmony/publish/submit_harmony_deadline..py @@ -0,0 +1,404 @@ +# -*- coding: utf-8 -*- +"""Submitting render job to Deadline.""" +import os +from pathlib import Path +from collections import OrderedDict +from zipfile import ZipFile, is_zipfile + +import attr +import pyblish.api + +import pype.lib.abstract_submit_deadline +from pype.lib.abstract_submit_deadline import DeadlineJobInfo +from avalon import api + + +class _ZipFile(ZipFile): + """Extended check for windows invalid characters.""" + + # this is extending default zipfile table for few invalid characters + # that can come from Mac + _windows_illegal_characters = ":<>|\"?*\r\n\x00" + _windows_illegal_name_trans_table = str.maketrans( + _windows_illegal_characters, + "_" * len(_windows_illegal_characters) + ) + + +@attr.s +class PluginInfo(object): + """Plugin info structure for Harmony Deadline plugin.""" + + SceneFile = attr.ib() + # Harmony version + Version = attr.ib() + + Camera = attr.ib(default="") + FieldOfView = attr.ib(default=41.11) + IsDatabase = attr.ib(default=False) + ResolutionX = attr.ib(default=1920) + ResolutionY = attr.ib(default=1080) + + # Resolution name preset, default + UsingResPreset = attr.ib(default=False) + ResolutionName = attr.ib(default="HDTV_1080p24") + + PreRenderInlineScript = attr.ib(default=None) + + # -------------------------------------------------- + _outputNode = attr.ib(factory=list) + + @property + def OutputNode(self): # noqa: N802 + """Return all output nodes formatted for Deadline. + + Returns: + dict: as `{'Output0Node', 'Top/renderFarmDefault'}` + + """ + out = {} + for index, v in enumerate(self._outputNode): + out["Output{}Node".format(index)] = v + return out + + @OutputNode.setter + def OutputNode(self, val): # noqa: N802 + self._outputNode.append(val) + + # -------------------------------------------------- + _outputType = attr.ib(factory=list) + + @property + def OutputType(self): # noqa: N802 + """Return output nodes type formatted for Deadline. + + Returns: + dict: as `{'Output0Type', 'Image'}` + + """ + out = {} + for index, v in enumerate(self._outputType): + out["Output{}Type".format(index)] = v + return out + + @OutputType.setter + def OutputType(self, val): # noqa: N802 + self._outputType.append(val) + + # -------------------------------------------------- + _outputLeadingZero = attr.ib(factory=list) + + @property + def OutputLeadingZero(self): # noqa: N802 + """Return output nodes type formatted for Deadline. + + Returns: + dict: as `{'Output0LeadingZero', '3'}` + + """ + out = {} + for index, v in enumerate(self._outputLeadingZero): + out["Output{}LeadingZero".format(index)] = v + return out + + @OutputLeadingZero.setter + def OutputLeadingZero(self, val): # noqa: N802 + self._outputLeadingZero.append(val) + + # -------------------------------------------------- + _outputFormat = attr.ib(factory=list) + + @property + def OutputFormat(self): # noqa: N802 + """Return output nodes format formatted for Deadline. + + Returns: + dict: as `{'Output0Type', 'PNG4'}` + + """ + out = {} + for index, v in enumerate(self._outputFormat): + out["Output{}Format".format(index)] = v + return out + + @OutputFormat.setter + def OutputFormat(self, val): # noqa: N802 + self._outputFormat.append(val) + + # -------------------------------------------------- + _outputStartFrame = attr.ib(factory=list) + + @property + def OutputStartFrame(self): # noqa: N802 + """Return start frame for output nodes formatted for Deadline. + + Returns: + dict: as `{'Output0StartFrame', '1'}` + + """ + out = {} + for index, v in enumerate(self._outputStartFrame): + out["Output{}StartFrame".format(index)] = v + return out + + @OutputStartFrame.setter + def OutputStartFrame(self, val): # noqa: N802 + self._outputStartFrame.append(val) + + # -------------------------------------------------- + _outputPath = attr.ib(factory=list) + + @property + def OutputPath(self): # noqa: N802 + """Return output paths for nodes formatted for Deadline. + + Returns: + dict: as `{'Output0Path', '/output/path'}` + + """ + out = {} + for index, v in enumerate(self._outputPath): + out["Output{}Path".format(index)] = v + return out + + @OutputPath.setter + def OutputPath(self, val): # noqa: N802 + self._outputPath.append(val) + + def set_output(self, node, image_format, output, + output_type="Image", zeros=3, start_frame=1): + """Helper to set output. + + This should be used instead of setting properties individually + as so index remain consistent. + + Args: + node (str): harmony write node name + image_format (str): format of output (PNG4, TIF, ...) + output (str): output path + output_type (str, optional): "Image" or "Movie" (not supported). + zeros (int, optional): Leading zeros (for 0001 = 3) + start_frame (int, optional): Sequence offset. + + """ + + self.OutputNode = node + self.OutputFormat = image_format + self.OutputPath = output + self.OutputType = output_type + self.OutputLeadingZero = zeros + self.OutputStartFrame = start_frame + + def serialize(self): + """Return all data serialized as dictionary. + + Returns: + OrderedDict: all serialized data. + + """ + def filter_data(a, v): + if a.name.startswith("_"): + return False + if v is None: + return False + return True + + serialized = attr.asdict( + self, dict_factory=OrderedDict, filter=filter_data) + serialized.update(self.OutputNode) + serialized.update(self.OutputFormat) + serialized.update(self.OutputPath) + serialized.update(self.OutputType) + serialized.update(self.OutputLeadingZero) + serialized.update(self.OutputStartFrame) + + return serialized + + +class HarmonySubmitDeadline( + pype.lib.abstract_submit_deadline.AbstractSubmitDeadline): + """Submit render write of Harmony scene to Deadline. + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable ``DEADLINE_REST_URL``. + + Note: + If Deadline configuration is not detected, this plugin will + be disabled. + + Attributes: + use_published (bool): Use published scene to render instead of the + one in work area. + + """ + + label = "Submit to Deadline" + order = pyblish.api.IntegratorOrder + 0.1 + hosts = ["harmony"] + families = ["renderlayer"] + if not os.environ.get("DEADLINE_REST_URL"): + optional = False + active = False + else: + optional = True + + use_published = False + primary_pool = "" + secondary_pool = "" + priority = 50 + chunk_size = 1000000 + + def get_job_info(self): + job_info = DeadlineJobInfo("Harmony") + job_info.Name = self._instance.data["name"] + job_info.Frames = "{}-{}".format( + self._instance.data["frameStart"], + self._instance.data["frameEnd"] + ) + # for now, get those from presets. Later on it should be + # configurable in Harmony UI directly. + job_info.Priority = self.priority + job_info.Pool = self.primary_pool + job_info.SecondaryPool = self.secondary_pool + job_info.ChunkSize = self.chunk_size + job_info.BatchName = os.path.basename(self._instance.data["source"]) + + keys = [ + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "PYPE_USERNAME", + "PYPE_DEV", + "PYPE_LOG_NO_COLORS" + ] + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **api.Session) + for key in keys: + val = environment.get(key) + if val: + job_info.EnvironmentKeyValue = "{key}={value}".format( + key=key, + value=val) + + return job_info + + def _unzip_scene_file(self, published_scene: Path) -> Path: + """Unzip scene zip file to its directory. + + Unzip scene file (if it is zip file) to its current directory and + return path to xstage file there. Xstage file is determined by its + name. + + Args: + published_scene (Path): path to zip file. + + Returns: + Path: The path to unzipped xstage. + """ + # if not zip, bail out. + if "zip" not in published_scene.suffix or not is_zipfile( + published_scene.as_posix() + ): + self.log.error("Published scene is not in zip.") + self.log.error(published_scene) + raise AssertionError("invalid scene format") + + xstage_path = ( + published_scene.parent + / published_scene.stem + / f"{published_scene.stem}.xstage" + ) + + unzip_dir = (published_scene.parent / published_scene.stem) + with _ZipFile(published_scene, "r") as zip_ref: + zip_ref.extractall(unzip_dir.as_posix()) + + # find any xstage files in directory, prefer the one with the same name + # as directory (plus extension) + xstage_files = [] + for scene in unzip_dir.iterdir(): + if scene.suffix == ".xstage": + xstage_files.append(scene) + + # there must be at least one (but maybe not more?) xstage file + if not xstage_files: + self.log.error("No xstage files found in zip") + raise AssertionError("Invalid scene archive") + + ideal_scene = False + # find the one with the same name as zip. In case there can be more + # then one xtage file. + for scene in xstage_files: + # if /foo/bar/baz.zip == /foo/bar/baz/baz.xstage + # ^^^ ^^^ + if scene.stem == published_scene.stem: + xstage_path = scene + ideal_scene = True + + # but sometimes xstage file has different name then zip - in that case + # use that one. + if not ideal_scene: + xstage_path = xstage_files[0] + + return xstage_path + + def get_plugin_info(self): + work_scene = Path(self._instance.data["source"]) + + # this is path to published scene workfile _ZIP_. Before + # rendering, we need to unzip it. + published_scene = Path( + self.from_published_scene(False)) + self.log.info(f"Processing {published_scene.as_posix()}") + xstage_path = self._unzip_scene_file(published_scene) + render_path = xstage_path.parent / "renders" + + # for submit_publish job to create .json file in + self._instance.data["outputDir"] = render_path + new_expected_files = [] + work_path_str = str(work_scene.parent.as_posix()) + render_path_str = str(render_path.as_posix()) + for file in self._instance.data["expectedFiles"]: + _file = str(Path(file).as_posix()) + new_expected_files.append( + _file.replace(work_path_str, render_path_str) + ) + + self._instance.data["source"] = str(published_scene.as_posix()) + self._instance.data["expectedFiles"] = new_expected_files + harmony_plugin_info = PluginInfo( + SceneFile=xstage_path.as_posix(), + Version=( + self._instance.context.data["harmonyVersion"].split(".")[0]), + FieldOfView=self._instance.context.data["FOV"], + ResolutionX=self._instance.data["resolutionWidth"], + ResolutionY=self._instance.data["resolutionHeight"] + ) + + dynamic_part = "{}.{}".format( + str(1).rjust(int(self._instance.data["leadingZeros"]) + 1, "0"), + self._instance.data["outputFormat"].lower()) + + harmony_plugin_info.set_output( + self._instance.data["setMembers"][0], + self._instance.data["outputFormat"], + self._instance.data["expectedFiles"][0].replace(dynamic_part, ''), + self._instance.data["outputType"], + self._instance.data["leadingZeros"], + self._instance.data["outputStartFrame"] + ) + + all_write_nodes = self._instance.context.data["all_write_nodes"] + disable_nodes = [] + for node in all_write_nodes: + # disable all other write nodes + if node != self._instance.data["setMembers"][0]: + disable_nodes.append("node.setEnable('{}', false)" + .format(node)) + harmony_plugin_info.PreRenderInlineScript = ';'.join(disable_nodes) + + return harmony_plugin_info.serialize() diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index 70e6f47721..17fc528cc0 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -21,7 +21,7 @@ class ValidateSceneSettingsRepair(pyblish.api.Action): pype.hosts.harmony.set_scene_settings( pype.hosts.harmony.get_asset_settings() ) - if not os.patch.exists(context.data["scenePath"]): + if not os.path.exists(context.data["scenePath"]): self.log.info("correcting scene name") scene_dir = os.path.dirname(context.data["currentFile"]) scene_path = os.path.join( From 01bbaec74ac8ee3b22582ade8483f75a9abca837 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jan 2021 18:36:44 +0100 Subject: [PATCH 02/14] Harmony to Deadline - 2x.develop version added custom plugin - for error_code handling and preRenderInline --- pype/plugins/harmony/publish/submit_harmony_deadline..py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/publish/submit_harmony_deadline..py b/pype/plugins/harmony/publish/submit_harmony_deadline..py index 88ab1bec1d..9af9f5b393 100644 --- a/pype/plugins/harmony/publish/submit_harmony_deadline..py +++ b/pype/plugins/harmony/publish/submit_harmony_deadline..py @@ -251,6 +251,7 @@ class HarmonySubmitDeadline( def get_job_info(self): job_info = DeadlineJobInfo("Harmony") job_info.Name = self._instance.data["name"] + job_info.Plugin = "HarmonyPype" job_info.Frames = "{}-{}".format( self._instance.data["frameStart"], self._instance.data["frameEnd"] @@ -382,7 +383,6 @@ class HarmonySubmitDeadline( dynamic_part = "{}.{}".format( str(1).rjust(int(self._instance.data["leadingZeros"]) + 1, "0"), self._instance.data["outputFormat"].lower()) - harmony_plugin_info.set_output( self._instance.data["setMembers"][0], self._instance.data["outputFormat"], From afce3b6932a6365d7d02084a46ccc8dcf1185599 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 14 Jan 2021 16:22:14 +0100 Subject: [PATCH 03/14] Harmon to Deadline - fix for usage of 'png4' etc. Fix - last character of file prefix is a number --- pype/plugins/harmony/publish/collect_farm_render.py | 8 +++++++- .../harmony/publish/submit_harmony_deadline..py | 10 ++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py index baf037491b..065d52c3fc 100644 --- a/pype/plugins/harmony/publish/collect_farm_render.py +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -76,10 +76,16 @@ class CollectFarmRender(pype.lib.abstract_collect_render. # is sequence start node on write node offsetting whole sequence? expected_files = [] + + # add '.' if last character of file prefix is a number + file_prefix = info[0] + last_char = file_prefix[-1] + if str.isdigit(last_char): + file_prefix += '.' for frame in range(start, end): expected_files.append( path / "{}{}.{}".format( - info[0], + file_prefix, str(frame).rjust(int(info[2]) + 1, "0"), ext ) diff --git a/pype/plugins/harmony/publish/submit_harmony_deadline..py b/pype/plugins/harmony/publish/submit_harmony_deadline..py index 9af9f5b393..dfb2702a8b 100644 --- a/pype/plugins/harmony/publish/submit_harmony_deadline..py +++ b/pype/plugins/harmony/publish/submit_harmony_deadline..py @@ -4,6 +4,7 @@ import os from pathlib import Path from collections import OrderedDict from zipfile import ZipFile, is_zipfile +import re import attr import pyblish.api @@ -380,13 +381,14 @@ class HarmonySubmitDeadline( ResolutionY=self._instance.data["resolutionHeight"] ) - dynamic_part = "{}.{}".format( - str(1).rjust(int(self._instance.data["leadingZeros"]) + 1, "0"), - self._instance.data["outputFormat"].lower()) + pattern = '[0]{' + str(self._instance.data["leadingZeros"]) + \ + '}1\.[a-zA-Z]{3}' + render_prefix = re.sub(pattern, '', + self._instance.data["expectedFiles"][0]) harmony_plugin_info.set_output( self._instance.data["setMembers"][0], self._instance.data["outputFormat"], - self._instance.data["expectedFiles"][0].replace(dynamic_part, ''), + render_prefix, self._instance.data["outputType"], self._instance.data["leadingZeros"], self._instance.data["outputStartFrame"] From 3b93fae6f7e317986503544936e262180e94f63b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jan 2021 16:02:15 +0100 Subject: [PATCH 04/14] Harmon to Deadline - fix for non alpha last character on write node Number or non alpha (._) last characters in write node create problems. Remove them as this value is internal and not used in final publish either way --- pype/plugins/harmony/publish/collect_farm_render.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py index 065d52c3fc..be9c3dd008 100644 --- a/pype/plugins/harmony/publish/collect_farm_render.py +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -77,11 +77,11 @@ class CollectFarmRender(pype.lib.abstract_collect_render. # is sequence start node on write node offsetting whole sequence? expected_files = [] - # add '.' if last character of file prefix is a number + # remove last char if last character of file prefix is a number file_prefix = info[0] - last_char = file_prefix[-1] - if str.isdigit(last_char): - file_prefix += '.' + while not str.isalpha(file_prefix[-1]): + file_prefix = file_prefix[:-1] + for frame in range(start, end): expected_files.append( path / "{}{}.{}".format( From 19a91b9ea7a348ffffe87eca576f24d0853fbe08 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jan 2021 17:06:59 +0100 Subject: [PATCH 05/14] Harmon to Deadline - fix for 17 It seems that Harmony 17 needs at least one '.' in file_prefix. Cannot be last character though! --- pype/plugins/harmony/publish/collect_farm_render.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py index be9c3dd008..e604cb0bc0 100644 --- a/pype/plugins/harmony/publish/collect_farm_render.py +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -77,10 +77,9 @@ class CollectFarmRender(pype.lib.abstract_collect_render. # is sequence start node on write node offsetting whole sequence? expected_files = [] - # remove last char if last character of file prefix is a number + # Harmony 17 needs at least one '.' in file_prefix, but not at end file_prefix = info[0] - while not str.isalpha(file_prefix[-1]): - file_prefix = file_prefix[:-1] + file_prefix += '.temp' for frame in range(start, end): expected_files.append( From 8355979cc991f48741590e72084ebca14f7a8eba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Jan 2021 13:05:46 +0100 Subject: [PATCH 06/14] Harmon to Deadline - fix last frame wasn't published --- pype/plugins/harmony/publish/collect_farm_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py index e604cb0bc0..aaf965d3c6 100644 --- a/pype/plugins/harmony/publish/collect_farm_render.py +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -81,7 +81,7 @@ class CollectFarmRender(pype.lib.abstract_collect_render. file_prefix = info[0] file_prefix += '.temp' - for frame in range(start, end): + for frame in range(start, end + 1): expected_files.append( path / "{}{}.{}".format( file_prefix, From 24d84b46e013fd43ad89dc5607edeaf1db92d367 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Jan 2021 15:01:22 +0100 Subject: [PATCH 07/14] Harmon to Deadline - performance speedup because of Harmony 17 extract_template is extremely slow in 17, copy full scene for a workfile --- pype/plugins/harmony/publish/extract_workfile.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pype/plugins/harmony/publish/extract_workfile.py b/pype/plugins/harmony/publish/extract_workfile.py index 09bf0db860..be0444f0e6 100644 --- a/pype/plugins/harmony/publish/extract_workfile.py +++ b/pype/plugins/harmony/publish/extract_workfile.py @@ -10,7 +10,7 @@ import pype.hosts.harmony class ExtractWorkfile(pype.api.Extractor): - """Extract the connected nodes to the composite instance.""" + """Extract and zip complete workfile folder into zip.""" label = "Extract Workfile" hosts = ["harmony"] @@ -18,15 +18,11 @@ class ExtractWorkfile(pype.api.Extractor): def process(self, instance): """Plugin entry point.""" - # Export template. - backdrops = harmony.send( - {"function": "Backdrop.backdrops", "args": ["Top"]} - )["result"] - nodes = instance.context.data.get("allNodes") staging_dir = self.staging_dir(instance) filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name)) - - pype.hosts.harmony.export_template(backdrops, nodes, filepath) + src = os.path.dirname(instance.context.data["currentFile"]) + self.log.info("Copying to {}".format(filepath)) + shutil.copytree(src, filepath) # Prep representation. os.chdir(staging_dir) From 55d447f1d6326778a47eaf8d32ad3eac5d27bc5e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 20 Jan 2021 18:26:52 +0100 Subject: [PATCH 08/14] Harmon to Deadline - added audio file collect --- pype/plugins/harmony/publish/collect_audio.py | 36 +++++++++++++++++++ .../publish/submit_harmony_deadline..py | 5 +++ 2 files changed, 41 insertions(+) create mode 100644 pype/plugins/harmony/publish/collect_audio.py diff --git a/pype/plugins/harmony/publish/collect_audio.py b/pype/plugins/harmony/publish/collect_audio.py new file mode 100644 index 0000000000..8ef9632ea6 --- /dev/null +++ b/pype/plugins/harmony/publish/collect_audio.py @@ -0,0 +1,36 @@ +import os +import pyblish.api + +import pyblish.api +from avalon import harmony + + +class CollectAudio(pyblish.api.InstancePlugin): + """ + Collect relative path for audio file to instance. + + Harmony api `getSoundtrackAll` returns useless path to temp folder, + for render on farm we look into 'audio' folder and select first file. + + Correct path needs to be calculated in `submit_harmony_deadline.py` + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = "Collect Audio" + hosts = ["harmony"] + families = ["renderlayer"] + + def process(self, instance): + audio_dir = os.path.join( + os.path.dirname(instance.context.data.get("currentFile")), 'audio') + if os.path.isdir(audio_dir): + for full_file_name in os.listdir(audio_dir): + file_name, file_ext = os.path.splitext(full_file_name) + + if file_ext not in ['.wav', '.mp3', '.aiff']: + self.log.error("Unsupported file {}.{}".format(file_name, + file_ext)) + + audio_file_path = os.path.join('audio', full_file_name) + self.log.error("audio_file_path {}".format(audio_file_path)) + instance.data["audioFile"] = audio_file_path diff --git a/pype/plugins/harmony/publish/submit_harmony_deadline..py b/pype/plugins/harmony/publish/submit_harmony_deadline..py index dfb2702a8b..e40ff02d08 100644 --- a/pype/plugins/harmony/publish/submit_harmony_deadline..py +++ b/pype/plugins/harmony/publish/submit_harmony_deadline..py @@ -370,6 +370,11 @@ class HarmonySubmitDeadline( _file.replace(work_path_str, render_path_str) ) + audio_file = self._instance.data.get("audioFile") + if audio_file: + abs_path = xstage_path.parent / audio_file + self._instance.context.data["audioFile"] = str(abs_path) + self._instance.data["source"] = str(published_scene.as_posix()) self._instance.data["expectedFiles"] = new_expected_files harmony_plugin_info = PluginInfo( From a0e3ed743ea109ac6c6a178311a2e157735ada3d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 21 Jan 2021 11:11:34 +0100 Subject: [PATCH 09/14] Harmon to Deadline - fix - wrong logging --- pype/plugins/harmony/publish/collect_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/publish/collect_audio.py b/pype/plugins/harmony/publish/collect_audio.py index 8ef9632ea6..27f8dfc0d6 100644 --- a/pype/plugins/harmony/publish/collect_audio.py +++ b/pype/plugins/harmony/publish/collect_audio.py @@ -32,5 +32,5 @@ class CollectAudio(pyblish.api.InstancePlugin): file_ext)) audio_file_path = os.path.join('audio', full_file_name) - self.log.error("audio_file_path {}".format(audio_file_path)) + self.log.debug("audio_file_path {}".format(audio_file_path)) instance.data["audioFile"] = audio_file_path From 638245c0daea58ce659f400386e973035e3f78b9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 21 Jan 2021 14:27:08 +0100 Subject: [PATCH 10/14] Harmon to Deadline - fix - skip resolution validation for renders Value in context.anatomyData is checked for [rR]ender presence --- pype/plugins/harmony/publish/validate_scene_settings.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index 17fc528cc0..5ab1b11ec9 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -40,6 +40,8 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): actions = [ValidateSceneSettingsRepair] frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] + # used for skipping resolution validation for render tasks + render_check_filter = ["render", "Render"] def process(self, instance): """Plugin entry point.""" @@ -65,6 +67,12 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): fps = float( "{:.2f}".format(instance.context.data.get("frameRate"))) + if any(string in instance.context.data['anatomyData']['task'] + for string in self.render_check_filter): + self.log.debug("Render task detected, resolution check skipped") + expected_settings.pop("resolutionWidth") + expected_settings.pop("resolutionHeight") + current_settings = { "fps": fps, "frameStart": instance.context.data.get("frameStart"), From c45f5f11b4d0cbeb1bd59c78ff84fbf14e52c82e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 21 Jan 2021 19:11:32 +0100 Subject: [PATCH 11/14] Harmon to Deadline - fix - remove 'Farm' from subset name Subset on farm must match subset from manually rendered --- pype/plugins/harmony/publish/collect_farm_render.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/harmony/publish/collect_farm_render.py b/pype/plugins/harmony/publish/collect_farm_render.py index aaf965d3c6..5925dafa72 100644 --- a/pype/plugins/harmony/publish/collect_farm_render.py +++ b/pype/plugins/harmony/publish/collect_farm_render.py @@ -126,12 +126,13 @@ class CollectFarmRender(pype.lib.abstract_collect_render. # TODO: handle pixel aspect and frame step # TODO: set Deadline stuff (pools, priority, etc. by presets) + subset_name = node.split("/")[1].replace('Farm', '') render_instance = HarmonyRenderInstance( version=version, time=api.time(), source=context.data["currentFile"], - label=node.split("/")[1], - subset=node.split("/")[1], + label=subset_name, + subset=subset_name, asset=api.Session["AVALON_ASSET"], attachTo=False, setMembers=[node], From 32ee745d1f0614ec517a02426adb9f171c9e20bc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 22 Jan 2021 11:48:41 +0100 Subject: [PATCH 12/14] Harmony to Deadline - fix if backslash is used in path Settings '64\bin' was mangled to '6in' --- pype/hooks/harmony/pre_launch_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hooks/harmony/pre_launch_args.py b/pype/hooks/harmony/pre_launch_args.py index 70c05eb352..f09cef9384 100644 --- a/pype/hooks/harmony/pre_launch_args.py +++ b/pype/hooks/harmony/pre_launch_args.py @@ -26,7 +26,7 @@ class HarmonyPrelaunchHook(PreLaunchHook): ( "import avalon.harmony;" "avalon.harmony.launch(\"{}\")" - ).format(harmony_executable) + ).format(harmony_executable.replace("\\", "/")) ] # Append as whole list as these areguments should not be separated From 9ff4be8f73c46b53a9fbfa95f1441925447e5c93 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 22 Jan 2021 12:38:33 +0100 Subject: [PATCH 13/14] Harmony to Deadline - fix usage of Settings for Pype3 --- pype/hosts/harmony/__init__.py | 7 ++++--- pype/settings/defaults/project_settings/harmony.json | 4 ++-- .../projects_schema/schema_project_harmony.json | 10 ++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 7ea261292e..390ede39d9 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -9,7 +9,7 @@ import avalon.tools.sceneinventory import pyblish.api from pype import lib -from pype.api import config +from pype.api import (get_current_project_settings) def set_scene_settings(settings): @@ -48,12 +48,13 @@ def get_asset_settings(): "resolutionWidth": resolution_width, "resolutionHeight": resolution_height } + settings = get_current_project_settings() try: skip_resolution_check = \ - config.get_presets()["harmony"]["general"]["skip_resolution_check"] + settings["harmony"]["general"]["skip_resolution_check"] skip_timelines_check = \ - config.get_presets()["harmony"]["general"]["skip_timelines_check"] + settings["harmony"]["general"]["skip_timelines_check"] except KeyError: skip_resolution_check = [] skip_timelines_check = [] diff --git a/pype/settings/defaults/project_settings/harmony.json b/pype/settings/defaults/project_settings/harmony.json index 5eca4f60eb..83d63d3392 100644 --- a/pype/settings/defaults/project_settings/harmony.json +++ b/pype/settings/defaults/project_settings/harmony.json @@ -1,7 +1,7 @@ { "publish": {}, "general": { - "skip_resolution_check": false, - "skip_timelines_check": false + "skip_resolution_check": [], + "skip_timelines_check": [] } } \ No newline at end of file diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json index 791a08cb8d..92ad39ac2c 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json @@ -19,14 +19,16 @@ "label": "General", "children": [ { - "type": "boolean", + "type": "list", "key": "skip_resolution_check", - "label": "Skip Resolution Check" + "object_type": "text", + "label": "Skip Resolution Check for Tasks" }, { - "type": "boolean", + "type": "list", "key": "skip_timelines_check", - "label": "Skip Timeliene Check" + "object_type": "text", + "label": "Skip Timeliene Check for Tasks" } ] } From b1b6e58110a94cb0ca33f95daf9087af650e92ad Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 22 Jan 2021 13:26:09 +0100 Subject: [PATCH 14/14] Harmony to Deadline - Hound --- pype/hosts/harmony/js/PypeHarmony.js | 2 +- pype/plugins/harmony/publish/collect_audio.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pype/hosts/harmony/js/PypeHarmony.js b/pype/hosts/harmony/js/PypeHarmony.js index 555a5ce8fd..9d05384461 100644 --- a/pype/hosts/harmony/js/PypeHarmony.js +++ b/pype/hosts/harmony/js/PypeHarmony.js @@ -213,4 +213,4 @@ PypeHarmony.getVersion = function() { about.getMajorVersion(), about.getMinorVersion() ]; -} +}; diff --git a/pype/plugins/harmony/publish/collect_audio.py b/pype/plugins/harmony/publish/collect_audio.py index 27f8dfc0d6..58521d6612 100644 --- a/pype/plugins/harmony/publish/collect_audio.py +++ b/pype/plugins/harmony/publish/collect_audio.py @@ -1,9 +1,6 @@ import os import pyblish.api -import pyblish.api -from avalon import harmony - class CollectAudio(pyblish.api.InstancePlugin): """