mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Implemented render publishing
This commit is contained in:
parent
8e1ec6d251
commit
6d787cadd1
7 changed files with 313 additions and 2 deletions
|
|
@ -47,6 +47,7 @@ def install():
|
|||
print("installing OpenPype for Unreal ...")
|
||||
print("-=" * 40)
|
||||
logger.info("installing OpenPype for Unreal")
|
||||
pyblish.api.register_host("unreal")
|
||||
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
|
||||
register_loader_plugin_path(str(LOAD_PATH))
|
||||
api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
|
||||
|
|
@ -416,3 +417,24 @@ def cast_map_to_str_dict(umap) -> dict:
|
|||
|
||||
"""
|
||||
return {str(key): str(value) for (key, value) in umap.items()}
|
||||
|
||||
|
||||
def get_subsequences(sequence: unreal.LevelSequence):
|
||||
"""Get list of subsequences from sequence.
|
||||
|
||||
Args:
|
||||
sequence (unreal.LevelSequence): Sequence
|
||||
|
||||
Returns:
|
||||
list(unreal.LevelSequence): List of subsequences
|
||||
|
||||
"""
|
||||
tracks = sequence.get_master_tracks()
|
||||
subscene_track = None
|
||||
for t in tracks:
|
||||
if t.get_class() == unreal.MovieSceneSubTrack.static_class():
|
||||
subscene_track = t
|
||||
break
|
||||
if subscene_track is not None and subscene_track.get_sections():
|
||||
return subscene_track.get_sections()
|
||||
return []
|
||||
|
|
|
|||
113
openpype/hosts/unreal/plugins/create/create_render.py
Normal file
113
openpype/hosts/unreal/plugins/create/create_render.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import unreal
|
||||
|
||||
from openpype.hosts.unreal.api import pipeline
|
||||
from openpype.hosts.unreal.api.plugin import Creator
|
||||
|
||||
|
||||
class CreateRender(Creator):
|
||||
"""Create instance for sequence for rendering"""
|
||||
|
||||
name = "unrealRender"
|
||||
label = "Unreal - Render"
|
||||
family = "render"
|
||||
icon = "cube"
|
||||
asset_types = ["LevelSequence"]
|
||||
|
||||
root = "/Game/OpenPype/PublishInstances"
|
||||
suffix = "_INS"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateRender, self).__init__(*args, **kwargs)
|
||||
|
||||
def process(self):
|
||||
subset = self.data["subset"]
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
# Get the master sequence and the master level.
|
||||
# There should be only one sequence and one level in the directory.
|
||||
filter = unreal.ARFilter(
|
||||
class_names=["LevelSequence"],
|
||||
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
|
||||
recursive_paths=False)
|
||||
sequences = ar.get_assets(filter)
|
||||
ms = sequences[0].get_editor_property('object_path')
|
||||
filter = unreal.ARFilter(
|
||||
class_names=["World"],
|
||||
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
|
||||
recursive_paths=False)
|
||||
levels = ar.get_assets(filter)
|
||||
ml = levels[0].get_editor_property('object_path')
|
||||
|
||||
selection = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
selection = [
|
||||
a.get_path_name() for a in sel_objects
|
||||
if a.get_class().get_name() in self.asset_types]
|
||||
else:
|
||||
selection.append(self.data['sequence'])
|
||||
|
||||
unreal.log(f"selection: {selection}")
|
||||
|
||||
path = f"{self.root}"
|
||||
unreal.EditorAssetLibrary.make_directory(path)
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
for a in selection:
|
||||
ms_obj = ar.get_asset_by_object_path(ms).get_asset()
|
||||
|
||||
seq_data = None
|
||||
|
||||
if a == ms:
|
||||
seq_data = {
|
||||
"sequence": ms_obj,
|
||||
"output": f"{ms_obj.get_name()}",
|
||||
"frame_range": (
|
||||
ms_obj.get_playback_start(), ms_obj.get_playback_end())
|
||||
}
|
||||
else:
|
||||
seq_data_list = [{
|
||||
"sequence": ms_obj,
|
||||
"output": f"{ms_obj.get_name()}",
|
||||
"frame_range": (
|
||||
ms_obj.get_playback_start(), ms_obj.get_playback_end())
|
||||
}]
|
||||
|
||||
for s in seq_data_list:
|
||||
subscenes = pipeline.get_subsequences(s.get('sequence'))
|
||||
|
||||
for ss in subscenes:
|
||||
curr_data = {
|
||||
"sequence": ss.get_sequence(),
|
||||
"output": (f"{s.get('output')}/"
|
||||
f"{ss.get_sequence().get_name()}"),
|
||||
"frame_range": (
|
||||
ss.get_start_frame(), ss.get_end_frame() - 1)
|
||||
}
|
||||
|
||||
if ss.get_sequence().get_path_name() == a:
|
||||
seq_data = curr_data
|
||||
break
|
||||
seq_data_list.append(curr_data)
|
||||
|
||||
if seq_data is not None:
|
||||
break
|
||||
|
||||
if not seq_data:
|
||||
continue
|
||||
|
||||
d = self.data.copy()
|
||||
d["members"] = [a]
|
||||
d["sequence"] = a
|
||||
d["master_sequence"] = ms
|
||||
d["master_level"] = ml
|
||||
d["output"] = seq_data.get('output')
|
||||
d["frameStart"] = seq_data.get('frame_range')[0]
|
||||
d["frameEnd"] = seq_data.get('frame_range')[1]
|
||||
|
||||
container_name = f"{subset}{self.suffix}"
|
||||
pipeline.create_publish_instance(
|
||||
instance=container_name, path=path)
|
||||
pipeline.imprint(f"{path}/{container_name}", d)
|
||||
|
|
@ -17,7 +17,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
label = "Collect Instances"
|
||||
order = pyblish.api.CollectorOrder
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
hosts = ["unreal"]
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectRemoveMarked(pyblish.api.ContextPlugin):
|
||||
"""Remove marked data
|
||||
|
||||
Remove instances that have 'remove' in their instance.data
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.499
|
||||
label = 'Remove Marked Instances'
|
||||
|
||||
def process(self, context):
|
||||
|
||||
self.log.debug(context)
|
||||
# make ftrack publishable
|
||||
instances_to_remove = []
|
||||
for instance in context:
|
||||
if instance.data.get('remove'):
|
||||
instances_to_remove.append(instance)
|
||||
|
||||
for instance in instances_to_remove:
|
||||
context.remove(instance)
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
from pathlib import Path
|
||||
from tkinter.font import families
|
||||
import unreal
|
||||
|
||||
import pyblish.api
|
||||
from openpype import lib
|
||||
from openpype.pipeline import legacy_create
|
||||
from openpype.hosts.unreal.api import pipeline
|
||||
|
||||
|
||||
class CollectRenderInstances(pyblish.api.InstancePlugin):
|
||||
""" This collector will try to find all the rendered frames.
|
||||
|
||||
"""
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["unreal"]
|
||||
families = ["render"]
|
||||
label = "Collect Render Instances"
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug("Preparing Rendering Instances")
|
||||
|
||||
context = instance.context
|
||||
|
||||
data = instance.data
|
||||
data['remove'] = True
|
||||
|
||||
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||
|
||||
sequence = ar.get_asset_by_object_path(
|
||||
data.get('sequence')).get_asset()
|
||||
|
||||
sequences = [{
|
||||
"sequence": sequence,
|
||||
"output": data.get('output'),
|
||||
"frame_range": (
|
||||
data.get('frameStart'), data.get('frameEnd'))
|
||||
}]
|
||||
|
||||
for s in sequences:
|
||||
self.log.debug(f"Processing: {s.get('sequence').get_name()}")
|
||||
subscenes = pipeline.get_subsequences(s.get('sequence'))
|
||||
|
||||
if subscenes:
|
||||
for ss in subscenes:
|
||||
sequences.append({
|
||||
"sequence": ss.get_sequence(),
|
||||
"output": (f"{s.get('output')}/"
|
||||
f"{ss.get_sequence().get_name()}"),
|
||||
"frame_range": (
|
||||
ss.get_start_frame(), ss.get_end_frame() - 1)
|
||||
})
|
||||
else:
|
||||
# Avoid creating instances for camera sequences
|
||||
if "_camera" not in s.get('sequence').get_name():
|
||||
seq = s.get('sequence')
|
||||
seq_name = seq.get_name()
|
||||
|
||||
new_instance = context.create_instance(
|
||||
f"{data.get('subset')}_"
|
||||
f"{seq_name}")
|
||||
new_instance[:] = seq_name
|
||||
|
||||
new_data = new_instance.data
|
||||
|
||||
new_data["asset"] = seq_name
|
||||
new_data["setMembers"] = seq_name
|
||||
new_data["family"] = "render"
|
||||
new_data["families"] = ["render", "review"]
|
||||
new_data["parent"] = data.get("parent")
|
||||
new_data["subset"] = f"{data.get('subset')}_{seq_name}"
|
||||
new_data["level"] = data.get("level")
|
||||
new_data["output"] = s.get('output')
|
||||
new_data["fps"] = seq.get_display_rate().numerator
|
||||
new_data["frameStart"] = s.get('frame_range')[0]
|
||||
new_data["frameEnd"] = s.get('frame_range')[1]
|
||||
new_data["sequence"] = seq.get_path_name()
|
||||
new_data["master_sequence"] = data["master_sequence"]
|
||||
new_data["master_level"] = data["master_level"]
|
||||
|
||||
self.log.debug(f"new instance data: {new_data}")
|
||||
|
||||
project_dir = unreal.Paths.project_dir()
|
||||
render_dir = (f"{project_dir}/Saved/MovieRenders/"
|
||||
f"{s.get('output')}")
|
||||
render_path = Path(render_dir)
|
||||
|
||||
frames = []
|
||||
|
||||
for x in render_path.iterdir():
|
||||
if x.is_file() and x.suffix == '.png':
|
||||
frames.append(str(x.name))
|
||||
|
||||
if "representations" not in new_instance.data:
|
||||
new_instance.data["representations"] = []
|
||||
|
||||
repr = {
|
||||
'frameStart': s.get('frame_range')[0],
|
||||
'frameEnd': s.get('frame_range')[1],
|
||||
'name': 'png',
|
||||
'ext': 'png',
|
||||
'files': frames,
|
||||
'stagingDir': render_dir,
|
||||
'tags': ['review']
|
||||
}
|
||||
new_instance.data["representations"].append(repr)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
|
||||
"""Ensure the sequence of frames is complete
|
||||
|
||||
The files found in the folder are checked against the frameStart and
|
||||
frameEnd of the instance. If the first or last file is not
|
||||
corresponding with the first or last frame it is flagged as invalid.
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Sequence Frames"
|
||||
families = ["render"]
|
||||
hosts = ["unreal"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug(instance.data)
|
||||
|
||||
representations = instance.data.get("representations")
|
||||
for repr in representations:
|
||||
frames = []
|
||||
for x in repr.get("files"):
|
||||
# Get frame number. The last one contains the file extension,
|
||||
# while the one before that is the frame number.
|
||||
# `lstrip` removes any leading zeros. `or "0"` is to tackle
|
||||
# the case where the frame number is "00".
|
||||
frame = int(str(x).split('.')[-2])
|
||||
frames.append(frame)
|
||||
frames.sort()
|
||||
current_range = (frames[0], frames[-1])
|
||||
required_range = (instance.data["frameStart"],
|
||||
instance.data["frameEnd"])
|
||||
|
||||
if current_range != required_range:
|
||||
raise ValueError(f"Invalid frame range: {current_range} - "
|
||||
f"expected: {required_range}")
|
||||
|
||||
assert len(frames) == int(frames[-1]) - int(frames[0]) + 1, \
|
||||
"Missing frames"
|
||||
|
|
@ -51,7 +51,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"resolve",
|
||||
"webpublisher",
|
||||
"aftereffects",
|
||||
"flame"
|
||||
"flame",
|
||||
"unreal"
|
||||
]
|
||||
|
||||
# Supported extensions
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue