Merge pull request #4362 from ynput/feature/OP-4617_Global-Update-rendered-frames-in-latest-version

Nuke: update rendered frames in latest version
This commit is contained in:
Petr Kalis 2023-01-27 14:51:25 +01:00 committed by GitHub
commit 816a2c42da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 19 deletions

View file

@ -1,6 +1,6 @@
import json
import pyblish.api
from openpype.hosts.aftereffects.api import list_instances
from openpype.hosts.aftereffects.api import AfterEffectsHost
class PreCollectRender(pyblish.api.ContextPlugin):
@ -25,7 +25,7 @@ class PreCollectRender(pyblish.api.ContextPlugin):
self.log.debug("Not applicable for New Publisher, skip")
return
for inst in list_instances():
for inst in AfterEffectsHost().list_instances():
if inst.get("creator_attributes"):
raise ValueError("Instance created in New publisher, "
"cannot be published in Pyblish.\n"

View file

@ -1,10 +1,12 @@
import os
import shutil
import pyblish.api
import clique
import nuke
from openpype.pipeline import publish
from openpype.lib import collect_frames
class NukeRenderLocal(publish.ExtractorColormanaged):
@ -13,6 +15,8 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
Extract the result of savers by starting a comp render
This will run the local render of Fusion.
Allows to use last published frames and overwrite only specific ones
(set in instance.data.get("frames_to_fix"))
"""
order = pyblish.api.ExtractorOrder
@ -21,7 +25,6 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
families = ["render.local", "prerender.local", "still.local"]
def process(self, instance):
families = instance.data["families"]
child_nodes = (
instance.data.get("transientData", {}).get("childNodes")
or instance
@ -32,17 +35,16 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
if x.Class() == "Write":
node = x
self.log.debug("instance collected: {}".format(instance.data))
node_subset_name = instance.data.get("name", None)
first_frame = instance.data.get("frameStartHandle", None)
last_frame = instance.data.get("frameEndHandle", None)
node_subset_name = instance.data["subset"]
self.log.info("Starting render")
self.log.info("Start frame: {}".format(first_frame))
self.log.info("End frame: {}".format(last_frame))
filenames = []
node_file = node["file"]
# Collecte expected filepaths for each frame
# Collect expected filepaths for each frame
# - for cases that output is still image is first created set of
# paths which is then sorted and converted to list
expected_paths = list(sorted({
@ -50,22 +52,37 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
for frame in range(first_frame, last_frame + 1)
}))
# Extract only filenames for representation
filenames = [
filenames.extend([
os.path.basename(filepath)
for filepath in expected_paths
]
])
# Ensure output directory exists.
out_dir = os.path.dirname(expected_paths[0])
if not os.path.exists(out_dir):
os.makedirs(out_dir)
# Render frames
nuke.execute(
str(node_subset_name),
int(first_frame),
int(last_frame)
)
frames_to_render = [(first_frame, last_frame)]
frames_to_fix = instance.data.get("frames_to_fix")
if instance.data.get("last_version_published_files") and frames_to_fix:
frames_to_render = self._get_frames_to_render(frames_to_fix)
anatomy = instance.context.data["anatomy"]
self._copy_last_published(anatomy, instance, out_dir,
filenames)
for render_first_frame, render_last_frame in frames_to_render:
self.log.info("Starting render")
self.log.info("Start frame: {}".format(render_first_frame))
self.log.info("End frame: {}".format(render_last_frame))
# Render frames
nuke.execute(
str(node_subset_name),
int(render_first_frame),
int(render_last_frame)
)
ext = node["file_type"].value()
colorspace = node["colorspace"].value()
@ -106,6 +123,7 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
out_dir
))
families = instance.data["families"]
# redefinition of families
if "render.local" in families:
instance.data['family'] = 'render'
@ -133,3 +151,58 @@ class NukeRenderLocal(publish.ExtractorColormanaged):
self.log.info('Finished render')
self.log.debug("_ instance.data: {}".format(instance.data))
def _copy_last_published(self, anatomy, instance, out_dir,
expected_filenames):
"""Copies last published files to temporary out_dir.
These are base of files which will be extended/fixed for specific
frames.
Renames published file to expected file name based on frame, eg.
test_project_test_asset_subset_v005.1001.exr > new_render.1001.exr
"""
last_published = instance.data["last_version_published_files"]
last_published_and_frames = collect_frames(last_published)
expected_and_frames = collect_frames(expected_filenames)
frames_and_expected = {v: k for k, v in expected_and_frames.items()}
for file_path, frame in last_published_and_frames.items():
file_path = anatomy.fill_root(file_path)
if not os.path.exists(file_path):
continue
target_file_name = frames_and_expected.get(frame)
if not target_file_name:
continue
out_path = os.path.join(out_dir, target_file_name)
self.log.debug("Copying '{}' -> '{}'".format(file_path, out_path))
shutil.copy(file_path, out_path)
# TODO shouldn't this be uncommented
# instance.context.data["cleanupFullPaths"].append(out_path)
def _get_frames_to_render(self, frames_to_fix):
"""Return list of frame range tuples to render
Args:
frames_to_fix (str): specific or range of frames to be rerendered
(1005, 1009-1010)
Returns:
(list): [(1005, 1005), (1009-1010)]
"""
frames_to_render = []
for frame_range in frames_to_fix.split(","):
if frame_range.isdigit():
render_first_frame = frame_range
render_last_frame = frame_range
elif '-' in frame_range:
frames = frame_range.split('-')
render_first_frame = int(frames[0])
render_last_frame = int(frames[1])
else:
raise ValueError("Wrong format of frames to fix {}"
.format(frames_to_fix))
frames_to_render.append((render_first_frame,
render_last_frame))
return frames_to_render

View file

@ -189,6 +189,6 @@ class FileTransaction(object):
def _same_paths(self, src, dst):
# handles same paths but with C:/project vs c:/project
if os.path.exists(src) and os.path.exists(dst):
return os.path.samefile(src, dst)
return os.stat(src) == os.stat(dst)
return src == dst

View file

@ -0,0 +1,80 @@
import pyblish.api
from openpype.lib.attribute_definitions import (
TextDef,
BoolDef
)
from openpype.pipeline.publish import OpenPypePyblishPluginMixin
from openpype.client.entities import (
get_last_version_by_subset_name,
get_representations
)
class CollectFramesFixDef(
pyblish.api.InstancePlugin,
OpenPypePyblishPluginMixin
):
"""Provides text field to insert frame(s) to be rerendered.
Published files of last version of an instance subset are collected into
instance.data["last_version_published_files"]. All these but frames
mentioned in text field will be reused for new version.
"""
order = pyblish.api.CollectorOrder + 0.495
label = "Collect Frames to Fix"
targets = ["local"]
hosts = ["nuke"]
families = ["render", "prerender"]
enabled = True
def process(self, instance):
attribute_values = self.get_attr_values_from_data(instance.data)
frames_to_fix = attribute_values.get("frames_to_fix")
rewrite_version = attribute_values.get("rewrite_version")
if frames_to_fix:
instance.data["frames_to_fix"] = frames_to_fix
subset_name = instance.data["subset"]
asset_name = instance.data["asset"]
project_entity = instance.data["projectEntity"]
project_name = project_entity["name"]
version = get_last_version_by_subset_name(project_name,
subset_name,
asset_name=asset_name)
if not version:
self.log.warning("No last version found, "
"re-render not possible")
return
representations = get_representations(project_name,
version_ids=[version["_id"]])
published_files = []
for repre in representations:
if repre["context"]["family"] not in self.families:
continue
for file_info in repre.get("files"):
published_files.append(file_info["path"])
instance.data["last_version_published_files"] = published_files
self.log.debug("last_version_published_files::{}".format(
instance.data["last_version_published_files"]))
if rewrite_version:
instance.data["version"] = version["name"]
# limits triggering version validator
instance.data.pop("latestVersion")
@classmethod
def get_attribute_defs(cls):
return [
TextDef("frames_to_fix", label="Frames to fix",
placeholder="5,10-15",
regex="[0-9,-]+"),
BoolDef("rewrite_version", label="Rewrite latest version",
default=False),
]

View file

@ -534,6 +534,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
template_data["representation"] = repre["name"]
template_data["ext"] = repre["ext"]
# allow overwriting existing version
template_data["version"] = version["name"]
# add template data for colorspaceData
if repre.get("colorspaceData"):
colorspace = repre["colorspaceData"]["colorspace"]