Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

TVPaint frame range definition #1425

Merged
merged 10 commits into from
Apr 30, 2021
9 changes: 5 additions & 4 deletions openpype/hosts/tvpaint/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ def set_context_settings(asset_doc=None):
handle_start = handles
handle_end = handles

frame_start -= int(handle_start)
frame_end += int(handle_end)
# Always start from 0 Mark In and set only Mark Out
mark_in = 0
mark_out = mark_in + (frame_end - frame_start) + handle_start + handle_end

execute_george("tv_markin {} set".format(frame_start - 1))
execute_george("tv_markout {} set".format(frame_end - 1))
execute_george("tv_markin {} set".format(mark_in))
execute_george("tv_markout {} set".format(mark_out))
37 changes: 37 additions & 0 deletions openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pyblish.api


class CollectOutputFrameRange(pyblish.api.ContextPlugin):
"""Collect frame start/end from context.

When instances are collected context does not contain `frameStart` and
`frameEnd` keys yet. They are collected in global plugin
`CollectAvalonEntities`.
"""
label = "Collect output frame range"
order = pyblish.api.CollectorOrder
hosts = ["tvpaint"]

def process(self, context):
for instance in context:
frame_start = instance.data.get("frameStart")
frame_end = instance.data.get("frameEnd")
if frame_start is not None and frame_end is not None:
self.log.debug(
"Instance {} already has set frames {}-{}".format(
str(instance), frame_start, frame_end
)
)
return

frame_start = context.data.get("frameStart")
frame_end = context.data.get("frameEnd")

instance.data["frameStart"] = frame_start
instance.data["frameEnd"] = frame_end

self.log.info(
"Set frames {}-{} on instance {} ".format(
frame_start, frame_end, str(instance)
)
)
3 changes: 0 additions & 3 deletions openpype/hosts/tvpaint/plugins/publish/collect_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ def process(self, context):

instance.data["publish"] = any_visible

instance.data["frameStart"] = context.data["sceneMarkIn"] + 1
instance.data["frameEnd"] = context.data["sceneMarkOut"] + 1

self.log.debug("Created instance: {}\n{}".format(
instance, json.dumps(instance.data, indent=4)
))
Expand Down
164 changes: 120 additions & 44 deletions openpype/hosts/tvpaint/plugins/publish/extract_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ def process(self, instance):
)

family_lowered = instance.data["family"].lower()
frame_start = instance.data["frameStart"]
frame_end = instance.data["frameEnd"]

filename_template = self._get_filename_template(frame_end)
mark_in = instance.context.data["sceneMarkIn"]
mark_out = instance.context.data["sceneMarkOut"]
# Frame start/end may be stored as float
frame_start = int(instance.data["frameStart"])
frame_end = int(instance.data["frameEnd"])

filename_template = self._get_filename_template(
# Use the biggest number
max(mark_out, frame_end)
)
ext = os.path.splitext(filename_template)[1].replace(".", "")

self.log.debug("Using file template \"{}\"".format(filename_template))
Expand All @@ -66,33 +72,46 @@ def process(self, instance):

if instance.data["family"] == "review":
repre_files, thumbnail_fullpath = self.render_review(
filename_template, output_dir, frame_start, frame_end
filename_template, output_dir,
mark_in, mark_out,
frame_start, frame_end
)
else:
# Render output
repre_files, thumbnail_fullpath = self.render(
filename_template, output_dir, frame_start, frame_end,
filename_template, output_dir,
mark_in, mark_out,
frame_start, frame_end,
filtered_layers
)

# Sequence of one frame
if not repre_files:
self.log.warning("Extractor did not create any output.")
return

# Fill tags and new families
tags = []
if family_lowered in ("review", "renderlayer"):
tags.append("review")

# Sequence of one frame
if len(repre_files) == 1:
single_file = len(repre_files) == 1
if single_file:
repre_files = repre_files[0]

new_repre = {
"name": ext,
"ext": ext,
"files": repre_files,
"stagingDir": output_dir,
"frameStart": frame_start,
"frameEnd": frame_end,
"tags": tags
}

if not single_file:
new_repre["frameStart"] = frame_start
new_repre["frameEnd"] = frame_end

self.log.debug("Creating new representation: {}".format(new_repre))

instance.data["representations"].append(new_repre)
Expand Down Expand Up @@ -134,7 +153,8 @@ def _get_filename_template(self, frame_end):
return "{{frame:0>{}}}".format(frame_padding) + ".png"

def render_review(
self, filename_template, output_dir, frame_start, frame_end
self, filename_template, output_dir, mark_in, mark_out,
frame_start, frame_end
):
""" Export images from TVPaint using `tv_savesequence` command.

Expand All @@ -154,10 +174,8 @@ def render_review(
self.log.debug("Preparing data for rendering.")
first_frame_filepath = os.path.join(
output_dir,
filename_template.format(frame=frame_start)
filename_template.format(frame=mark_in)
)
mark_in = frame_start - 1
mark_out = frame_end - 1

george_script_lines = [
"tv_SaveMode \"PNG\"",
Expand All @@ -170,24 +188,40 @@ def render_review(
]
lib.execute_george_through_file("\n".join(george_script_lines))

output = []
reversed_repre_filepaths = []
marks_range = range(mark_out, mark_in - 1, -1)
frames_range = range(frame_end, frame_start - 1, -1)
for mark, frame in zip(marks_range, frames_range):
new_filename = filename_template.format(frame=frame)
new_filepath = os.path.join(output_dir, new_filename)
reversed_repre_filepaths.append(new_filepath)

if mark != frame:
old_filename = filename_template.format(frame=mark)
old_filepath = os.path.join(output_dir, old_filename)
os.rename(old_filepath, new_filepath)

repre_filepaths = list(reversed(reversed_repre_filepaths))
repre_files = [
os.path.basename(path)
for path in repre_filepaths
]

first_frame_filepath = None
for frame in range(frame_start, frame_end + 1):
filename = filename_template.format(frame=frame)
output.append(filename)
if first_frame_filepath is None:
first_frame_filepath = os.path.join(output_dir, filename)
if repre_filepaths:
first_frame_filepath = repre_filepaths[0]

thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg")
if first_frame_filepath and os.path.exists(first_frame_filepath):
source_img = Image.open(first_frame_filepath)
thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255))
thumbnail_obj.paste(source_img)
thumbnail_obj.save(thumbnail_filepath)
return output, thumbnail_filepath
return repre_files, thumbnail_filepath

def render(
self, filename_template, output_dir, frame_start, frame_end, layers
self, filename_template, output_dir, mark_in, mark_out,
frame_start, frame_end, layers
):
""" Export images from TVPaint.

Expand Down Expand Up @@ -219,14 +253,11 @@ def render(
# Sort layer positions in reverse order
sorted_positions = list(reversed(sorted(layers_by_position.keys())))
if not sorted_positions:
return
return [], None

self.log.debug("Collecting pre/post behavior of individual layers.")
behavior_by_layer_id = lib.get_layers_pre_post_behavior(layer_ids)

mark_in_index = frame_start - 1
mark_out_index = frame_end - 1

tmp_filename_template = "pos_{pos}." + filename_template

files_by_position = {}
Expand All @@ -239,36 +270,67 @@ def render(
tmp_filename_template,
output_dir,
behavior,
mark_in_index,
mark_out_index
mark_in,
mark_out
)
files_by_position[position] = files_by_frames
if files_by_frames:
files_by_position[position] = files_by_frames
else:
self.log.warning((
"Skipped layer \"{}\". Probably out of Mark In/Out range."
).format(layer["name"]))

if not files_by_position:
layer_names = set(layer["name"] for layer in layers)
joined_names = ", ".join(
["\"{}\"".format(name) for name in layer_names]
)
self.log.warning(
"Layers {} do not have content in range {} - {}".format(
joined_names, mark_in, mark_out
)
)
return [], None

output_filepaths = self._composite_files(
output_filepaths_by_frame = self._composite_files(
files_by_position,
mark_in_index,
mark_out_index,
mark_in,
mark_out,
filename_template,
output_dir
)
self._cleanup_tmp_files(files_by_position)

reversed_repre_filepaths = []
marks_range = range(mark_out, mark_in - 1, -1)
frames_range = range(frame_end, frame_start - 1, -1)
for mark, frame in zip(marks_range, frames_range):
new_filename = filename_template.format(frame=frame)
new_filepath = os.path.join(output_dir, new_filename)
reversed_repre_filepaths.append(new_filepath)

if mark != frame:
old_filepath = output_filepaths_by_frame[mark]
os.rename(old_filepath, new_filepath)

repre_filepaths = list(reversed(reversed_repre_filepaths))
repre_files = [
os.path.basename(path)
for path in repre_filepaths
]

thumbnail_src_filepath = None
thumbnail_filepath = None
if output_filepaths:
thumbnail_src_filepath = tuple(sorted(output_filepaths))[0]
if repre_filepaths:
thumbnail_src_filepath = repre_filepaths[0]

thumbnail_filepath = None
if thumbnail_src_filepath and os.path.exists(thumbnail_src_filepath):
source_img = Image.open(thumbnail_src_filepath)
thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg")
thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255))
thumbnail_obj.paste(source_img)
thumbnail_obj.save(thumbnail_filepath)

repre_files = [
os.path.basename(path)
for path in output_filepaths
]
return repre_files, thumbnail_filepath

def _render_layer(
Expand All @@ -283,6 +345,22 @@ def _render_layer(
layer_id = layer["layer_id"]
frame_start_index = layer["frame_start"]
frame_end_index = layer["frame_end"]

pre_behavior = behavior["pre"]
post_behavior = behavior["post"]

# Check if layer is before mark in
if frame_end_index < mark_in_index:
# Skip layer if post behavior is "none"
if post_behavior == "none":
return {}

# Check if layer is after mark out
elif frame_start_index > mark_out_index:
# Skip layer if pre behavior is "none"
if pre_behavior == "none":
return {}

exposure_frames = lib.get_exposure_frames(
layer_id, frame_start_index, frame_end_index
)
Expand Down Expand Up @@ -341,8 +419,6 @@ def _render_layer(
self.log.debug("Filled frames {}".format(str(_debug_filled_frames)))

# Fill frames by pre/post behavior of layer
pre_behavior = behavior["pre"]
post_behavior = behavior["post"]
self.log.debug((
"Completing image sequence of layer by pre/post behavior."
" PRE: {} | POST: {}"
Expand Down Expand Up @@ -535,14 +611,14 @@ def _composite_files(
process_count -= 1

processes = {}
output_filepaths = []
output_filepaths_by_frame = {}
missing_frame_paths = []
random_frame_path = None
for frame_idx in sorted(images_by_frame.keys()):
image_filepaths = images_by_frame[frame_idx]
output_filename = filename_template.format(frame=frame_idx + 1)
output_filepath = os.path.join(output_dir, output_filename)
output_filepaths.append(output_filepath)
output_filepaths_by_frame[frame_idx] = output_filepath

# Store information about missing frame and skip
if not image_filepaths:
Expand All @@ -566,7 +642,7 @@ def _composite_files(
random_frame_path = output_filepath

self.log.info(
"Running {} compositing processes - this mey take a while.".format(
"Running {} compositing processes - this may take a while.".format(
len(processes)
)
)
Expand Down Expand Up @@ -608,7 +684,7 @@ def _composite_files(
transparent_filepath = filepath
else:
self._copy_image(transparent_filepath, filepath)
return output_filepaths
return output_filepaths_by_frame

def _cleanup_tmp_files(self, files_by_position):
"""Remove temporary files that were used for compositing."""
Expand Down
Loading