mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
* OP-5657 - add artist control for review in AfterEffects Artist can disable review to be created for particular publish. * OP-5657 - add artist control for review in AfterEffects Removed configuration for Deadline, should be controlled by what is on instance. * OP-5657 - handle legacy instances Legacy instances wont't have mark_for_review in creator_attributes. Set to true as by default we always want review. * OP-5657 - remove explicit review for all AE Now handled directly on instance * OP-5657 - fix - cannot remove now Without this 'review' wont be added to tags on representation. Eventually this should be refactored. Control on whole instance, eg. disabling review, should be enough. * OP-5657 - fix - correct host name used * OP-5657 - fix - correct handling of review On local renders review should be added only from families, not from older approach through Settings. Farm instance cannot have review in families or extract_review would get triggered even locally. * OP-5657 - refactor - changed label * OP-5657 - Hound * OP-5657 - added explicitly skipping review Instance might have set 'review' to False, which should explicitly skip review (might come from Publisher where artist can disable/enable review on an instance). * OP-5657 - updated setting of review variable instance.data.review == False >> explicitly set to do not create review. Keep None to let logic decide. * OP-5657 - fix adding review flag * OP-5657 - updated test Removed review for second instance. * OP-5657 - refactor to context plugin * OP-5657 - tie thumbnail to review for local render Produce thumbnail only when review should be created to synchronize state with farm rendering. Move creation of thumnbail out of this plugin to general plugin to limit duplication of logic.
270 lines
9.3 KiB
Python
270 lines
9.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Collect render template.
|
|
|
|
TODO: use @dataclass when times come.
|
|
|
|
"""
|
|
from abc import abstractmethod
|
|
|
|
import attr
|
|
import six
|
|
|
|
import pyblish.api
|
|
|
|
from openpype.pipeline import legacy_io
|
|
from .publish_plugins import AbstractMetaContextPlugin
|
|
|
|
|
|
@attr.s
|
|
class RenderInstance(object):
|
|
"""Data collected by collectors.
|
|
|
|
This data class later on passed to collected instances.
|
|
Those attributes are required later on.
|
|
|
|
"""
|
|
|
|
# metadata
|
|
version = attr.ib() # instance version
|
|
time = attr.ib() # time of instance creation (get_formatted_current_time)
|
|
source = attr.ib() # path to source scene file
|
|
label = attr.ib() # label to show in GUI
|
|
subset = attr.ib() # subset name
|
|
task = attr.ib() # task name
|
|
asset = attr.ib() # asset name (AVALON_ASSET)
|
|
attachTo = attr.ib() # subset name to attach render to
|
|
setMembers = attr.ib() # list of nodes/members producing render output
|
|
publish = attr.ib() # bool, True to publish instance
|
|
name = attr.ib() # instance name
|
|
|
|
# format settings
|
|
resolutionWidth = attr.ib() # resolution width (1920)
|
|
resolutionHeight = attr.ib() # resolution height (1080)
|
|
pixelAspect = attr.ib() # pixel aspect (1.0)
|
|
|
|
# time settings
|
|
frameStart = attr.ib() # start frame
|
|
frameEnd = attr.ib() # start end
|
|
frameStep = attr.ib() # frame step
|
|
|
|
handleStart = attr.ib(default=None) # start frame
|
|
handleEnd = attr.ib(default=None) # start frame
|
|
|
|
# for software (like Harmony) where frame range cannot be set by DB
|
|
# handles need to be propagated if exist
|
|
ignoreFrameHandleCheck = attr.ib(default=False)
|
|
|
|
# --------------------
|
|
# With default values
|
|
# metadata
|
|
renderer = attr.ib(default="") # renderer - can be used in Deadline
|
|
review = attr.ib(default=None) # False - explicitly skip review
|
|
priority = attr.ib(default=50) # job priority on farm
|
|
|
|
family = attr.ib(default="renderlayer")
|
|
families = attr.ib(default=["renderlayer"]) # list of families
|
|
# True if should be rendered on farm, eg not integrate
|
|
farm = attr.ib(default=False)
|
|
|
|
# format settings
|
|
multipartExr = attr.ib(default=False) # flag for multipart exrs
|
|
convertToScanline = attr.ib(default=False) # flag for exr conversion
|
|
|
|
tileRendering = attr.ib(default=False) # bool: treat render as tiles
|
|
tilesX = attr.ib(default=0) # number of tiles in X
|
|
tilesY = attr.ib(default=0) # number of tiles in Y
|
|
|
|
# submit_publish_job
|
|
toBeRenderedOn = attr.ib(default=None)
|
|
deadlineSubmissionJob = attr.ib(default=None)
|
|
anatomyData = attr.ib(default=None)
|
|
outputDir = attr.ib(default=None)
|
|
context = attr.ib(default=None)
|
|
|
|
@frameStart.validator
|
|
def check_frame_start(self, _, value):
|
|
"""Validate if frame start is not larger then end."""
|
|
if value > self.frameEnd:
|
|
raise ValueError("frameStart must be smaller "
|
|
"or equal then frameEnd")
|
|
|
|
@frameEnd.validator
|
|
def check_frame_end(self, _, value):
|
|
"""Validate if frame end is not less then start."""
|
|
if value < self.frameStart:
|
|
raise ValueError("frameEnd must be smaller "
|
|
"or equal then frameStart")
|
|
|
|
@tilesX.validator
|
|
def check_tiles_x(self, _, value):
|
|
"""Validate if tile x isn't less then 1."""
|
|
if not self.tileRendering:
|
|
return
|
|
if value < 1:
|
|
raise ValueError("tile X size cannot be less then 1")
|
|
|
|
if value == 1 and self.tilesY == 1:
|
|
raise ValueError("both tiles X a Y sizes are set to 1")
|
|
|
|
@tilesY.validator
|
|
def check_tiles_y(self, _, value):
|
|
"""Validate if tile y isn't less then 1."""
|
|
if not self.tileRendering:
|
|
return
|
|
if value < 1:
|
|
raise ValueError("tile Y size cannot be less then 1")
|
|
|
|
if value == 1 and self.tilesX == 1:
|
|
raise ValueError("both tiles X a Y sizes are set to 1")
|
|
|
|
|
|
@six.add_metaclass(AbstractMetaContextPlugin)
|
|
class AbstractCollectRender(pyblish.api.ContextPlugin):
|
|
"""Gather all publishable render layers from renderSetup."""
|
|
|
|
order = pyblish.api.CollectorOrder + 0.01
|
|
label = "Collect Render"
|
|
sync_workfile_version = False
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Constructor."""
|
|
super(AbstractCollectRender, self).__init__(*args, **kwargs)
|
|
self._file_path = None
|
|
self._asset = legacy_io.Session["AVALON_ASSET"]
|
|
self._context = None
|
|
|
|
def process(self, context):
|
|
"""Entry point to collector."""
|
|
self._context = context
|
|
for instance in context:
|
|
# make sure workfile instance publishing is enabled
|
|
try:
|
|
if "workfile" in instance.data["families"]:
|
|
instance.data["publish"] = True
|
|
# TODO merge renderFarm and render.farm
|
|
if ("renderFarm" in instance.data["families"] or
|
|
"render.farm" in instance.data["families"]):
|
|
instance.data["remove"] = True
|
|
except KeyError:
|
|
# be tolerant if 'families' is missing.
|
|
pass
|
|
|
|
self._file_path = context.data["currentFile"].replace("\\", "/")
|
|
|
|
render_instances = self.get_instances(context)
|
|
for render_instance in render_instances:
|
|
exp_files = self.get_expected_files(render_instance)
|
|
assert exp_files, "no file names were generated, this is bug"
|
|
|
|
# if we want to attach render to subset, check if we have AOV's
|
|
# in expectedFiles. If so, raise error as we cannot attach AOV
|
|
# (considered to be subset on its own) to another subset
|
|
if render_instance.attachTo:
|
|
assert isinstance(exp_files, list), (
|
|
"attaching multiple AOVs or renderable cameras to "
|
|
"subset is not supported"
|
|
)
|
|
|
|
frame_start_render = int(render_instance.frameStart)
|
|
frame_end_render = int(render_instance.frameEnd)
|
|
if (render_instance.ignoreFrameHandleCheck or
|
|
int(context.data['frameStartHandle']) == frame_start_render
|
|
and int(context.data['frameEndHandle']) == frame_end_render): # noqa: W503, E501
|
|
|
|
handle_start = context.data['handleStart']
|
|
handle_end = context.data['handleEnd']
|
|
frame_start = context.data['frameStart']
|
|
frame_end = context.data['frameEnd']
|
|
frame_start_handle = context.data['frameStartHandle']
|
|
frame_end_handle = context.data['frameEndHandle']
|
|
else:
|
|
handle_start = 0
|
|
handle_end = 0
|
|
frame_start = frame_start_render
|
|
frame_end = frame_end_render
|
|
frame_start_handle = frame_start_render
|
|
frame_end_handle = frame_end_render
|
|
|
|
data = {
|
|
"handleStart": handle_start,
|
|
"handleEnd": handle_end,
|
|
"frameStart": frame_start,
|
|
"frameEnd": frame_end,
|
|
"frameStartHandle": frame_start_handle,
|
|
"frameEndHandle": frame_end_handle,
|
|
"byFrameStep": int(render_instance.frameStep),
|
|
|
|
"author": context.data["user"],
|
|
# Add source to allow tracing back to the scene from
|
|
# which was submitted originally
|
|
"expectedFiles": exp_files,
|
|
}
|
|
if self.sync_workfile_version:
|
|
data["version"] = context.data["version"]
|
|
|
|
# add additional data
|
|
data = self.add_additional_data(data)
|
|
render_instance_dict = attr.asdict(render_instance)
|
|
|
|
instance = context.create_instance(render_instance.name)
|
|
instance.data["label"] = render_instance.label
|
|
instance.data.update(render_instance_dict)
|
|
instance.data.update(data)
|
|
|
|
self.post_collecting_action()
|
|
|
|
@abstractmethod
|
|
def get_instances(self, context):
|
|
"""Get all renderable instances and their data.
|
|
|
|
Args:
|
|
context (pyblish.api.Context): Context object.
|
|
|
|
Returns:
|
|
list of :class:`RenderInstance`: All collected renderable instances
|
|
(like render layers, write nodes, etc.)
|
|
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_expected_files(self, render_instance):
|
|
"""Get list of expected files.
|
|
|
|
Returns:
|
|
list: expected files. This can be either simple list of files with
|
|
their paths, or list of dictionaries, where key is name of AOV
|
|
for example and value is list of files for that AOV.
|
|
|
|
Example::
|
|
|
|
['/path/to/file.001.exr', '/path/to/file.002.exr']
|
|
|
|
or as dictionary:
|
|
|
|
[
|
|
{
|
|
"beauty": ['/path/to/beauty.001.exr', ...],
|
|
"mask": ['/path/to/mask.001.exr']
|
|
}
|
|
]
|
|
|
|
"""
|
|
pass
|
|
|
|
def add_additional_data(self, data):
|
|
"""Add additional data to collected instance.
|
|
|
|
This can be overridden by host implementation to add custom
|
|
additional data.
|
|
|
|
"""
|
|
return data
|
|
|
|
def post_collecting_action(self):
|
|
"""Execute some code after collection is done.
|
|
|
|
This is useful for example for restoring current render layer.
|
|
|
|
"""
|
|
pass
|