AfterEffects: add review flag to each instance (#4884)

* 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.
This commit is contained in:
Petr Kalis 2023-05-04 12:16:58 +02:00 committed by GitHub
parent 3d870ef794
commit e7aa413038
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 116 additions and 63 deletions

View file

@ -26,12 +26,9 @@ class RenderCreator(Creator):
create_allow_context_change = True create_allow_context_change = True
def __init__(self, project_settings, *args, **kwargs): # Settings
super(RenderCreator, self).__init__(project_settings, *args, **kwargs) default_variants = []
self._default_variants = (project_settings["aftereffects"] mark_for_review = True
["create"]
["RenderCreator"]
["defaults"])
def create(self, subset_name_from_ui, data, pre_create_data): def create(self, subset_name_from_ui, data, pre_create_data):
stub = api.get_stub() # only after After Effects is up stub = api.get_stub() # only after After Effects is up
@ -82,28 +79,40 @@ class RenderCreator(Creator):
use_farm = pre_create_data["farm"] use_farm = pre_create_data["farm"]
new_instance.creator_attributes["farm"] = use_farm new_instance.creator_attributes["farm"] = use_farm
review = pre_create_data["mark_for_review"]
new_instance.creator_attributes["mark_for_review"] = review
api.get_stub().imprint(new_instance.id, api.get_stub().imprint(new_instance.id,
new_instance.data_to_store()) new_instance.data_to_store())
self._add_instance_to_context(new_instance) self._add_instance_to_context(new_instance)
stub.rename_item(comp.id, subset_name) stub.rename_item(comp.id, subset_name)
def get_default_variants(self):
return self._default_variants
def get_instance_attr_defs(self):
return [BoolDef("farm", label="Render on farm")]
def get_pre_create_attr_defs(self): def get_pre_create_attr_defs(self):
output = [ output = [
BoolDef("use_selection", default=True, label="Use selection"), BoolDef("use_selection", default=True, label="Use selection"),
BoolDef("use_composition_name", BoolDef("use_composition_name",
label="Use composition name in subset"), label="Use composition name in subset"),
UISeparatorDef(), UISeparatorDef(),
BoolDef("farm", label="Render on farm") BoolDef("farm", label="Render on farm"),
BoolDef(
"mark_for_review",
label="Review",
default=self.mark_for_review
)
] ]
return output return output
def get_instance_attr_defs(self):
return [
BoolDef("farm", label="Render on farm"),
BoolDef(
"mark_for_review",
label="Review",
default=False
)
]
def get_icon(self): def get_icon(self):
return resources.get_openpype_splash_filepath() return resources.get_openpype_splash_filepath()
@ -143,6 +152,13 @@ class RenderCreator(Creator):
api.get_stub().rename_item(comp_id, api.get_stub().rename_item(comp_id,
new_comp_name) new_comp_name)
def apply_settings(self, project_settings, system_settings):
plugin_settings = (
project_settings["aftereffects"]["create"]["RenderCreator"]
)
self.mark_for_review = plugin_settings["mark_for_review"]
def get_detail_description(self): def get_detail_description(self):
return """Creator for Render instances return """Creator for Render instances
@ -201,4 +217,7 @@ class RenderCreator(Creator):
instance_data["creator_attributes"] = {"farm": is_old_farm} instance_data["creator_attributes"] = {"farm": is_old_farm}
instance_data["family"] = self.family instance_data["family"] = self.family
if instance_data["creator_attributes"].get("mark_for_review") is None:
instance_data["creator_attributes"]["mark_for_review"] = True
return instance_data return instance_data

View file

@ -88,10 +88,11 @@ class CollectAERender(publish.AbstractCollectRender):
raise ValueError("No file extension set in Render Queue") raise ValueError("No file extension set in Render Queue")
render_item = render_q[0] render_item = render_q[0]
instance_families = inst.data.get("families", [])
subset_name = inst.data["subset"] subset_name = inst.data["subset"]
instance = AERenderInstance( instance = AERenderInstance(
family="render", family="render",
families=inst.data.get("families", []), families=instance_families,
version=version, version=version,
time="", time="",
source=current_file, source=current_file,
@ -109,6 +110,7 @@ class CollectAERender(publish.AbstractCollectRender):
tileRendering=False, tileRendering=False,
tilesX=0, tilesX=0,
tilesY=0, tilesY=0,
review="review" in instance_families,
frameStart=frame_start, frameStart=frame_start,
frameEnd=frame_end, frameEnd=frame_end,
frameStep=1, frameStep=1,
@ -139,6 +141,9 @@ class CollectAERender(publish.AbstractCollectRender):
instance.toBeRenderedOn = "deadline" instance.toBeRenderedOn = "deadline"
instance.renderer = "aerender" instance.renderer = "aerender"
instance.farm = True # to skip integrate instance.farm = True # to skip integrate
if "review" in instance.families:
# to skip ExtractReview locally
instance.families.remove("review")
instances.append(instance) instances.append(instance)
instances_to_remove.append(inst) instances_to_remove.append(inst)
@ -218,15 +223,4 @@ class CollectAERender(publish.AbstractCollectRender):
if fam not in instance.families: if fam not in instance.families:
instance.families.append(fam) instance.families.append(fam)
settings = get_project_settings(os.getenv("AVALON_PROJECT"))
reviewable_subset_filter = (settings["deadline"]
["publish"]
["ProcessSubmittedJobOnFarm"]
["aov_filter"].get(self.hosts[0]))
for aov_pattern in reviewable_subset_filter:
if re.match(aov_pattern, instance.subset):
instance.families.append("review")
instance.review = True
break
return instance return instance

View file

@ -0,0 +1,25 @@
"""
Requires:
None
Provides:
instance -> family ("review")
"""
import pyblish.api
class CollectReview(pyblish.api.ContextPlugin):
"""Add review to families if instance created with 'mark_for_review' flag
"""
label = "Collect Review"
hosts = ["aftereffects"]
order = pyblish.api.CollectorOrder + 0.1
def process(self, context):
for instance in context:
creator_attributes = instance.data.get("creator_attributes") or {}
if (
creator_attributes.get("mark_for_review")
and "review" not in instance.data["families"]
):
instance.data["families"].append("review")

View file

@ -66,33 +66,9 @@ class ExtractLocalRender(publish.Extractor):
first_repre = not representations first_repre = not representations
if instance.data["review"] and first_repre: if instance.data["review"] and first_repre:
repre_data["tags"] = ["review"] repre_data["tags"] = ["review"]
thumbnail_path = os.path.join(staging_dir, files[0])
instance.data["thumbnailSource"] = thumbnail_path
representations.append(repre_data) representations.append(repre_data)
instance.data["representations"] = representations instance.data["representations"] = representations
ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
# Generate thumbnail.
thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg")
args = [
ffmpeg_path, "-y",
"-i", first_file_path,
"-vf", "scale=300:-1",
"-vframes", "1",
thumbnail_path
]
self.log.debug("Thumbnail args:: {}".format(args))
try:
output = run_subprocess(args)
except TypeError:
self.log.warning("Error in creating thumbnail")
six.reraise(*sys.exc_info())
instance.data["representations"].append({
"name": "thumbnail",
"ext": "jpg",
"files": os.path.basename(thumbnail_path),
"stagingDir": staging_dir,
"tags": ["thumbnail"]
})

View file

@ -438,7 +438,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"Finished copying %i files" % len(resource_files)) "Finished copying %i files" % len(resource_files))
def _create_instances_for_aov( def _create_instances_for_aov(
self, instance_data, exp_files, additional_data self, instance_data, exp_files, additional_data, do_not_add_review
): ):
"""Create instance for each AOV found. """Create instance for each AOV found.
@ -449,6 +449,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
instance_data (pyblish.plugin.Instance): skeleton data for instance instance_data (pyblish.plugin.Instance): skeleton data for instance
(those needed) later by collector (those needed) later by collector
exp_files (list): list of expected files divided by aovs exp_files (list): list of expected files divided by aovs
additional_data (dict):
do_not_add_review (bool): explicitly skip review
Returns: Returns:
list of instances list of instances
@ -514,8 +516,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
app = os.environ.get("AVALON_APP", "") app = os.environ.get("AVALON_APP", "")
preview = False
if isinstance(col, list): if isinstance(col, list):
render_file_name = os.path.basename(col[0]) render_file_name = os.path.basename(col[0])
else: else:
@ -532,6 +532,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
new_instance = deepcopy(instance_data) new_instance = deepcopy(instance_data)
new_instance["subset"] = subset_name new_instance["subset"] = subset_name
new_instance["subsetGroup"] = group_name new_instance["subsetGroup"] = group_name
preview = preview and not do_not_add_review
if preview: if preview:
new_instance["review"] = True new_instance["review"] = True
@ -591,7 +593,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
self.log.debug("instances:{}".format(instances)) self.log.debug("instances:{}".format(instances))
return instances return instances
def _get_representations(self, instance, exp_files): def _get_representations(self, instance, exp_files, do_not_add_review):
"""Create representations for file sequences. """Create representations for file sequences.
This will return representations of expected files if they are not This will return representations of expected files if they are not
@ -602,6 +604,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
instance (dict): instance data for which we are instance (dict): instance data for which we are
setting representations setting representations
exp_files (list): list of expected files exp_files (list): list of expected files
do_not_add_review (bool): explicitly skip review
Returns: Returns:
list of representations list of representations
@ -651,6 +654,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
if instance.get("slate"): if instance.get("slate"):
frame_start -= 1 frame_start -= 1
preview = preview and not do_not_add_review
rep = { rep = {
"name": ext, "name": ext,
"ext": ext, "ext": ext,
@ -705,6 +709,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
preview = match_aov_pattern( preview = match_aov_pattern(
host_name, self.aov_filter, remainder host_name, self.aov_filter, remainder
) )
preview = preview and not do_not_add_review
if preview: if preview:
rep.update({ rep.update({
"fps": instance.get("fps"), "fps": instance.get("fps"),
@ -820,8 +825,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
families = [family] families = [family]
# pass review to families if marked as review # pass review to families if marked as review
do_not_add_review = False
if data.get("review"): if data.get("review"):
families.append("review") families.append("review")
elif data.get("review") == False:
self.log.debug("Instance has review explicitly disabled.")
do_not_add_review = True
instance_skeleton_data = { instance_skeleton_data = {
"family": family, "family": family,
@ -977,7 +986,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
instances = self._create_instances_for_aov( instances = self._create_instances_for_aov(
instance_skeleton_data, instance_skeleton_data,
data.get("expectedFiles"), data.get("expectedFiles"),
additional_data additional_data,
do_not_add_review
) )
self.log.info("got {} instance{}".format( self.log.info("got {} instance{}".format(
len(instances), len(instances),
@ -986,7 +996,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
else: else:
representations = self._get_representations( representations = self._get_representations(
instance_skeleton_data, instance_skeleton_data,
data.get("expectedFiles") data.get("expectedFiles"),
do_not_add_review
) )
if "representations" not in instance_skeleton_data.keys(): if "representations" not in instance_skeleton_data.keys():

View file

@ -58,7 +58,7 @@ class RenderInstance(object):
# With default values # With default values
# metadata # metadata
renderer = attr.ib(default="") # renderer - can be used in Deadline renderer = attr.ib(default="") # renderer - can be used in Deadline
review = attr.ib(default=False) # generate review from instance (bool) review = attr.ib(default=None) # False - explicitly skip review
priority = attr.ib(default=50) # job priority on farm priority = attr.ib(default=50) # job priority on farm
family = attr.ib(default="renderlayer") family = attr.ib(default="renderlayer")

View file

@ -13,10 +13,14 @@
"RenderCreator": { "RenderCreator": {
"defaults": [ "defaults": [
"Main" "Main"
] ],
"mark_for_review": true
} }
}, },
"publish": { "publish": {
"CollectReview": {
"enabled": true
},
"ValidateSceneSettings": { "ValidateSceneSettings": {
"enabled": true, "enabled": true,
"optional": true, "optional": true,

View file

@ -40,7 +40,13 @@
"label": "Default Variants", "label": "Default Variants",
"object_type": "text", "object_type": "text",
"docstring": "Fill default variant(s) (like 'Main' or 'Default') used in subset name creation." "docstring": "Fill default variant(s) (like 'Main' or 'Default') used in subset name creation."
} },
{
"type": "boolean",
"key": "mark_for_review",
"label": "Review",
"default": true
}
] ]
} }
] ]
@ -51,6 +57,21 @@
"key": "publish", "key": "publish",
"label": "Publish plugins", "label": "Publish plugins",
"children": [ "children": [
{
"type": "dict",
"collapsible": true,
"key": "CollectReview",
"label": "Collect Review",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled",
"default": true
}
]
},
{ {
"type": "dict", "type": "dict",
"collapsible": true, "collapsible": true,

View file

@ -9,6 +9,9 @@ log = logging.getLogger("test_publish_in_aftereffects")
class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestClass): # noqa class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestClass): # noqa
"""est case for DL publishing in AfterEffects with multiple compositions. """est case for DL publishing in AfterEffects with multiple compositions.
Workfile contains 2 prepared `render` instances. First has review set,
second doesn't.
Uses generic TestCase to prepare fixtures for test data, testing DBs, Uses generic TestCase to prepare fixtures for test data, testing DBs,
env vars. env vars.
@ -68,7 +71,7 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla
name="renderTest_taskMain2")) name="renderTest_taskMain2"))
failures.append( failures.append(
DBAssert.count_of_types(dbcon, "representation", 7)) DBAssert.count_of_types(dbcon, "representation", 5))
additional_args = {"context.subset": "workfileTest_task", additional_args = {"context.subset": "workfileTest_task",
"context.ext": "aep"} "context.ext": "aep"}
@ -105,13 +108,13 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla
additional_args = {"context.subset": "renderTest_taskMain2", additional_args = {"context.subset": "renderTest_taskMain2",
"name": "thumbnail"} "name": "thumbnail"}
failures.append( failures.append(
DBAssert.count_of_types(dbcon, "representation", 1, DBAssert.count_of_types(dbcon, "representation", 0,
additional_args=additional_args)) additional_args=additional_args))
additional_args = {"context.subset": "renderTest_taskMain2", additional_args = {"context.subset": "renderTest_taskMain2",
"name": "png_exr"} "name": "png_exr"}
failures.append( failures.append(
DBAssert.count_of_types(dbcon, "representation", 1, DBAssert.count_of_types(dbcon, "representation", 0,
additional_args=additional_args)) additional_args=additional_args))
assert not any(failures) assert not any(failures)