Merge pull request #911 from pypeclub/3/feature/harmony-deadline-submission

Harmony deadline submission
This commit is contained in:
Milan Kolar 2021-01-22 21:55:50 +01:00 committed by GitHub
commit a3fdff91dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 774 additions and 37 deletions

View file

@ -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

View file

@ -9,7 +9,7 @@ import avalon.tools.sceneinventory
import pyblish.api
from pype import lib
from pype.api import get_current_project_settings
from pype.api import (get_current_project_settings)
def set_scene_settings(settings):
@ -48,20 +48,13 @@ def get_asset_settings():
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height
}
settings = get_current_project_settings()
try:
skip_resolution_check = (
get_current_project_settings()
["harmony"]
["general"]
["skip_resolution_check"]
)
skip_timelines_check = (
get_current_project_settings()
["harmony"]
["general"]
["skip_timelines_check"]
)
skip_resolution_check = \
settings["harmony"]["general"]["skip_resolution_check"]
skip_timelines_check = \
settings["harmony"]["general"]["skip_timelines_check"]
except KeyError:
skip_resolution_check = []
skip_timelines_check = []

View file

@ -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()
];
};

View file

@ -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();

View file

@ -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 = [

View file

@ -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]]
}
)

View file

@ -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",

View file

@ -0,0 +1,33 @@
import os
import pyblish.api
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.debug("audio_file_path {}".format(audio_file_path))
instance.data["audioFile"] = audio_file_path

View file

@ -0,0 +1,176 @@
# -*- 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 = []
# Harmony 17 needs at least one '.' in file_prefix, but not at end
file_prefix = info[0]
file_prefix += '.temp'
for frame in range(start, end + 1):
expected_files.append(
path / "{}{}.{}".format(
file_prefix,
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)
subset_name = node.split("/")[1].replace('Farm', '')
render_instance = HarmonyRenderInstance(
version=version,
time=api.time(),
source=context.data["currentFile"],
label=subset_name,
subset=subset_name,
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

View file

@ -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)

View file

@ -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)
})

View file

@ -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])

View file

@ -26,7 +26,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
"label": basename,
"name": basename,
"family": family,
"families": [],
"families": [family],
"representations": [],
"asset": os.environ["AVALON_ASSET"]
})

View file

@ -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.

View file

@ -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)

View file

@ -0,0 +1,411 @@
# -*- 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 re
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.Plugin = "HarmonyPype"
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)
)
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(
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"]
)
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"],
render_prefix,
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()

View file

@ -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(
@ -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"),

View file

@ -1,7 +1,7 @@
{
"publish": {},
"general": {
"skip_resolution_check": false,
"skip_timelines_check": false
"skip_resolution_check": [],
"skip_timelines_check": []
}
}

View file

@ -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"
}
]
}