diff --git a/pype/plugins/harmony/publish/collect_scene.py b/pype/plugins/harmony/publish/collect_scene.py new file mode 100644 index 0000000000..e24e327afc --- /dev/null +++ b/pype/plugins/harmony/publish/collect_scene.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +"""Collect scene data.""" +import os + +import pyblish.api +from avalon import harmony + + +class CollectScene(pyblish.api.ContextPlugin): + """Collect basic scene information.""" + + label = "Scene Data" + order = pyblish.api.CollectorOrder + hosts = ["harmony"] + + def process(self, context): + + sig = harmony.signature() + func = """function %s() + { + return [ + about.getApplicationPath(), + scene.currentProjectPath(), + scene.currentScene(), + scene.getFrameRate(), + scene.getStartFrame(), + scene.getStopFrame(), + sound.getSoundtrackAll().path(), + scene.defaultResolutionX(), + scene.defaultResolutionY() + ] + } + %s + """ % (sig, sig) + result = harmony.send( + {"function": func, "args": []} + )["result"] + + context.data["applicationPath"] = result[0] + context.data["scenePath"] = os.path.join( + result[1], result[2] + ".xstage") + context.data["frameRate"] = result[3] + context.data["frameStart"] = result[4] + context.data["frameEnd"] = result[5] + context.data["audioPath"] = result[6] + context.data["resolutionWidth"] = result[7] + context.data["resolutionHeight"] = result[8] + + all_nodes = harmony.send( + {"function": "node.subNodes", "args": ["Top"]} + )["result"] + + context.data["allNodes"] = all_nodes diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 9d3ae33e23..98a47de2f6 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -21,30 +21,17 @@ class ExtractRender(pyblish.api.InstancePlugin): def process(self, instance): # Collect scene data. - sig = harmony.signature() - func = """function %s(write_node) - { - return [ - about.getApplicationPath(), - scene.currentProjectPath(), - scene.currentScene(), - scene.getFrameRate(), - scene.getStartFrame(), - scene.getStopFrame(), - sound.getSoundtrackAll().path() - ] - } - %s - """ % (sig, sig) - result = harmony.send( - {"function": func, "args": [instance[0]]} - )["result"] - application_path = result[0] - scene_path = os.path.join(result[1], result[2] + ".xstage") - frame_rate = result[3] - frame_start = result[4] - frame_end = result[5] - audio_path = result[6] + + application_path = instance.context.data.get("applicationPath") + scene_path = instance.context.data.get("scenePath") + frame_rate = instance.context.data.get("frameRate") + frame_start = instance.context.data.get("frameStart") + frame_end = instance.context.data.get("frameEnd") + audio_path = instance.context.data.get("audioPath") + + if audio_path and os.path.exists(audio_path): + self.log.info(f"Using audio from {audio_path}") + instance.data["audio"] = [{"filename": audio_path}] instance.data["fps"] = frame_rate @@ -57,7 +44,7 @@ class ExtractRender(pyblish.api.InstancePlugin): } %s """ % (sig, sig) - result = harmony.send( + harmony.send( { "function": func, "args": [instance[0], path + "/" + instance.data["name"]] @@ -67,6 +54,7 @@ class ExtractRender(pyblish.api.InstancePlugin): # Execute rendering. Ignoring error cause Harmony returns error code # always. + self.log.info(f"running [ {application_path} -batch {scene_path}") proc = subprocess.Popen( [application_path, "-batch", scene_path], stdout=subprocess.PIPE, @@ -74,12 +62,16 @@ class ExtractRender(pyblish.api.InstancePlugin): stdin=subprocess.PIPE ) output, error = proc.communicate() + self.log.info("Click on the line below to see more details.") self.log.info(output.decode("utf-8")) # Collect rendered files. - self.log.debug(path) + self.log.debug(f"collecting from: {path}") files = os.listdir(path) - self.log.debug(files) + assert files, ( + "No rendered files found, render failed." + ) + self.log.debug(f"files there: {files}") collections, remainder = clique.assemble(files, minimum_items=1) assert not remainder, ( "There should not be a remainder for {0}: {1}".format( diff --git a/pype/plugins/harmony/publish/extract_template.py b/pype/plugins/harmony/publish/extract_template.py index d6851e4027..33e0341c8d 100644 --- a/pype/plugins/harmony/publish/extract_template.py +++ b/pype/plugins/harmony/publish/extract_template.py @@ -15,9 +15,9 @@ class ExtractTemplate(pype.api.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) - filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name)) + filepath = os.path.join(staging_dir, f"{instance.name}.tpl") - self.log.info("Outputting template to {}".format(staging_dir)) + self.log.info(f"Outputting template to {staging_dir}") dependencies = [] self.get_dependencies(instance[0], dependencies) @@ -30,9 +30,7 @@ class ExtractTemplate(pype.api.Extractor): unique_backdrops = [backdrops[x] for x in set(backdrops.keys())] # Get non-connected nodes within backdrops. - all_nodes = harmony.send( - {"function": "node.subNodes", "args": ["Top"]} - )["result"] + all_nodes = instance.context.data.get("allNodes") for node in [x for x in all_nodes if x not in dependencies]: within_unique_backdrops = bool( [x for x in self.get_backdrops(node) if x in unique_backdrops] @@ -52,15 +50,15 @@ class ExtractTemplate(pype.api.Extractor): # Prep representation. os.chdir(staging_dir) shutil.make_archive( - "{}".format(instance.name), + f"{instance.name}", "zip", - os.path.join(staging_dir, "{}.tpl".format(instance.name)) + os.path.join(staging_dir, f"{instance.name}.tpl") ) representation = { "name": "tpl", "ext": "zip", - "files": "{}.zip".format(instance.name), + "files": f"{instance.name}.zip", "stagingDir": staging_dir } instance.data["representations"] = [representation] diff --git a/pype/plugins/harmony/publish/extract_workfile.py b/pype/plugins/harmony/publish/extract_workfile.py index 3c5af11021..09bf0db860 100644 --- a/pype/plugins/harmony/publish/extract_workfile.py +++ b/pype/plugins/harmony/publish/extract_workfile.py @@ -1,5 +1,8 @@ +# -*- coding: utf-8 -*- +"""Extract work file.""" import os import shutil +from zipfile import ZipFile import pype.api from avalon import harmony @@ -14,13 +17,12 @@ class ExtractWorkfile(pype.api.Extractor): families = ["workfile"] def process(self, instance): + """Plugin entry point.""" # Export template. backdrops = harmony.send( {"function": "Backdrop.backdrops", "args": ["Top"]} )["result"] - nodes = harmony.send( - {"function": "node.subNodes", "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)) @@ -29,15 +31,19 @@ class ExtractWorkfile(pype.api.Extractor): # Prep representation. os.chdir(staging_dir) shutil.make_archive( - "{}".format(instance.name), + f"{instance.name}", "zip", - os.path.join(staging_dir, "{}.tpl".format(instance.name)) + os.path.join(staging_dir, f"{instance.name}.tpl") ) + # Check if archive is ok + with ZipFile(os.path.basename(f"{instance.name}.zip")) as zr: + if zr.testzip() is not None: + raise Exception("File archive is corrupted.") representation = { "name": "tpl", "ext": "zip", - "files": "{}.zip".format(instance.name), + "files": f"{instance.name}.zip", "stagingDir": staging_dir } instance.data["representations"] = [representation] diff --git a/pype/plugins/harmony/publish/validate_audio.py b/pype/plugins/harmony/publish/validate_audio.py index cc8d2cdc35..b949b0a6e6 100644 --- a/pype/plugins/harmony/publish/validate_audio.py +++ b/pype/plugins/harmony/publish/validate_audio.py @@ -10,7 +10,6 @@ class ValidateAudio(pyblish.api.InstancePlugin): If you are sure that you want to send render without audio, you can disable this validator before clicking on "publish" - """ order = pyblish.api.ValidatorOrder diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index fbeedeab77..70e6f47721 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +"""Validate scene settings.""" +import os import json import pyblish.api @@ -14,9 +17,17 @@ class ValidateSceneSettingsRepair(pyblish.api.Action): on = "failed" def process(self, context, plugin): + """Repair action entry point.""" pype.hosts.harmony.set_scene_settings( pype.hosts.harmony.get_asset_settings() ) + if not os.patch.exists(context.data["scenePath"]): + self.log.info("correcting scene name") + scene_dir = os.path.dirname(context.data["currentFile"]) + scene_path = os.path.join( + scene_dir, os.path.basename(scene_dir) + ".xstage" + ) + harmony.save_scene_as(scene_path) class ValidateSceneSettings(pyblish.api.InstancePlugin): @@ -31,6 +42,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] def process(self, instance): + """Plugin entry point.""" expected_settings = pype.hosts.harmony.get_asset_settings() self.log.info(expected_settings) @@ -46,20 +58,20 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): for string in self.frame_check_filter): expected_settings.pop("frameEnd") - sig = harmony.signature() - func = """function %s() - { - return { - "fps": scene.getFrameRate(), - "frameStart": scene.getStartFrame(), - "frameEnd": scene.getStopFrame(), - "resolutionWidth": scene.defaultResolutionX(), - "resolutionHeight": scene.defaultResolutionY() - }; + # handle case where ftrack uses only two decimal places + # 23.976023976023978 vs. 23.98 + fps = instance.context.data.get("frameRate") + if isinstance(instance.context.data.get("frameRate"), float): + fps = float( + "{:.2f}".format(instance.context.data.get("frameRate"))) + + current_settings = { + "fps": fps, + "frameStart": instance.context.data.get("frameStart"), + "frameEnd": instance.context.data.get("frameEnd"), + "resolutionWidth": instance.context.data.get("resolutionWidth"), + "resolutionHeight": instance.context.data.get("resolutionHeight"), } - %s - """ % (sig, sig) - current_settings = harmony.send({"function": func})["result"] invalid_settings = [] for key, value in expected_settings.items(): @@ -74,3 +86,6 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): json.dumps(invalid_settings, sort_keys=True, indent=4) ) assert not invalid_settings, msg + assert os.path.exists(instance.context.data.get("scenePath")), ( + "Scene file not found (saved under wrong name)" + )