mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2917 from simonebarbieri/feature/unreal-render_publishing
This commit is contained in:
commit
6fd689305c
9 changed files with 254 additions and 62 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))
|
||||
register_creator_plugin_path(str(CREATE_PATH))
|
||||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@ 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 = []
|
||||
|
||||
|
|
@ -70,14 +70,9 @@ def start_rendering():
|
|||
# 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(),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
@ -32,13 +29,13 @@ class CreateRender(Creator):
|
|||
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"):
|
||||
|
|
@ -46,61 +43,69 @@ class CreateRender(Creator):
|
|||
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()
|
||||
d["output"] = seq_data.get('output')
|
||||
d["frameStart"] = seq_data.get('frame_range')[0]
|
||||
d["frameEnd"] = seq_data.get('frame_range')[1]
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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,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)
|
||||
|
|
@ -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,)
|
||||
|
|
@ -51,7 +51,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"resolve",
|
||||
"webpublisher",
|
||||
"aftereffects",
|
||||
"flame"
|
||||
"flame",
|
||||
"unreal"
|
||||
]
|
||||
|
||||
# Supported extensions
|
||||
|
|
|
|||
1
repos/avalon-core
Submodule
1
repos/avalon-core
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 64491fbbcf89ba2a0b3a20d67d7486c6142232b3
|
||||
Loading…
Add table
Add a link
Reference in a new issue