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

Commit

Permalink
Merge pull request #2917 from simonebarbieri/feature/unreal-render_pu…
Browse files Browse the repository at this point in the history
…blishing
  • Loading branch information
mkolar authored Apr 28, 2022
2 parents 16fab3d + 99631d8 commit 6fd6893
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 62 deletions.
22 changes: 22 additions & 0 deletions openpype/hosts/unreal/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
register_creator_plugin_path(str(CREATE_PATH))
Expand Down Expand Up @@ -392,3 +393,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 []
15 changes: 5 additions & 10 deletions openpype/hosts/unreal/api/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,20 @@ def start_rendering():

sequences = [{
"sequence": sequence,
"output": f"{i['subset']}/{sequence.get_name()}",
"output": f"{i['output']}",
"frame_range": (
int(float(i["startFrame"])),
int(float(i["endFrame"])) + 1)
int(float(i["frameStart"])),
int(float(i["frameEnd"])) + 1)
}]
render_list = []

# Get all the sequences to render. If there are subsequences,
# add them and their frame ranges to the render list. We also
# use the names for the output paths.
for s in sequences:
tracks = s.get('sequence').get_master_tracks()
subscene_track = None
for t in tracks:
if t.get_class() == unreal.MovieSceneSubTrack.static_class():
subscene_track = t
if subscene_track is not None and subscene_track.get_sections():
subscenes = subscene_track.get_sections()
subscenes = pipeline.get_subsequences(s.get('sequence'))

if subscenes:
for ss in subscenes:
sequences.append({
"sequence": ss.get_sequence(),
Expand Down
105 changes: 55 additions & 50 deletions openpype/hosts/unreal/plugins/create/create_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ class CreateRender(Creator):
icon = "cube"
asset_types = ["LevelSequence"]

root = "/Game/AvalonInstances"
root = "/Game/OpenPype/PublishInstances"
suffix = "_INS"

def __init__(self, *args, **kwargs):
super(CreateRender, self).__init__(*args, **kwargs)

def process(self):
name = self.data["subset"]
subset = self.data["subset"]

ar = unreal.AssetRegistryHelpers.get_asset_registry()

Expand All @@ -32,75 +29,83 @@ def process(self):
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
recursive_paths=False)
sequences = ar.get_assets(filter)
ms = sequences[0].object_path
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].object_path
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("selection: {}".format(selection))
# instantiate(self.root, name, self.data, selection, self.suffix)
# container_name = "{}{}".format(name, self.suffix)
unreal.log(f"selection: {selection}")

# if we specify assets, create new folder and move them there. If not,
# just create empty folder
# new_name = pipeline.create_folder(self.root, name)
path = "{}/{}".format(self.root, name)
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
asset = ar.get_asset_by_object_path(a).get_asset()
asset_name = asset.get_name()

# Get frame range. We need to go through the hierarchy and check
# the frame range for the children.
asset_data = legacy_io.find_one({
"type": "asset",
"name": asset_name
})
id = asset_data.get('_id')

elements = list(
legacy_io.find({"type": "asset", "data.visualParent": id}))

if elements:
start_frames = []
end_frames = []
for e in elements:
start_frames.append(e.get('data').get('clipIn'))
end_frames.append(e.get('data').get('clipOut'))

elements.extend(legacy_io.find({
"type": "asset",
"data.visualParent": e.get('_id')
}))

min_frame = min(start_frames)
max_frame = max(end_frames)
else:
min_frame = asset_data.get('data').get('clipIn')
max_frame = asset_data.get('data').get('clipOut')

d["startFrame"] = min_frame
d["endFrame"] = max_frame
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"{asset_name}{self.suffix}"
container_name = f"{subset}{self.suffix}"
pipeline.create_publish_instance(
instance=container_name, path=path)
pipeline.imprint("{}/{}".format(path, container_name), d)
pipeline.imprint(f"{path}/{container_name}", d)
2 changes: 1 addition & 1 deletion openpype/hosts/unreal/plugins/publish/collect_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
24 changes: 24 additions & 0 deletions openpype/hosts/unreal/plugins/publish/collect_remove_marked.py
Original file line number Diff line number Diff line change
@@ -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)
103 changes: 103 additions & 0 deletions openpype/hosts/unreal/plugins/publish/collect_render_instances.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from pathlib import Path
import unreal

import pyblish.api
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)
41 changes: 41 additions & 0 deletions openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import clique

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):
representations = instance.data.get("representations")
for repr in representations:
patterns = [clique.PATTERNS["frames"]]
collections, remainder = clique.assemble(
repr["files"], minimum_items=1, patterns=patterns)

assert not remainder, "Must not have remainder"
assert len(collections) == 1, "Must detect single collection"
collection = collections[0]
frames = list(collection.indexes)

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}")

missing = collection.holes().indexes
assert not missing, "Missing frames: %s" % (missing,)
3 changes: 2 additions & 1 deletion openpype/plugins/publish/extract_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
"resolve",
"webpublisher",
"aftereffects",
"flame"
"flame",
"unreal"
]

# Supported extensions
Expand Down
Loading

0 comments on commit 6fd6893

Please sign in to comment.